2022.0727学习Java笔记之多线程编程(二)

多线程的操作方法

多线程的操作方法基本上都在Thread类之中定义的,所以研究方法也就是一个Thread类的翻译,那么下面主要看三组方法。

1.线程的命名和取得

  在整个多线程的操作之中,名称是最重要的,因为多线程的运行状态是不确定的,那么系统只能根据名字来判断出是那一个线程,所以在为线程命名的时候不要有重复,更不要修改。
  在Thread类里面提供了如下的几个方法进行名称操作:。

  • 构造方法:public Thread(Runnable target, String name);·
  • 设置名字:public final void setName(String name);·
  • 取得名字:public final String getName()。

问题是线程的运行状态不确定,所以要想取得线程的名字,只能够取得当前正在执行的线程。那么在这种情况下,如果要想取得当前的线程对象,就必须依靠Thread类的方法:public static Thread currentThread()。

范例:取得线程名字

package cn.mldn.demo;class MyThread implements Runnable{public void run(){   //线程的主方法System.out.println(Thread.currentThread().getName());}
}
public class TextDemo {public static void main(String[] args){MyThread mt = new MyThread();new Thread(mt).start() ;new Thread(mt).start() ;new Thread(mt,"带名字").start() ;}
}

发现此时如果设置了线程名称,那么就会返回设置的数据。如果没有设置线程名字会自动的进行命名,以保证每一个线程一定有个名字。

范例:观察如下代码

package cn.mldn.demo;class MyThread implements Runnable{public void run(){   //线程的主方法System.out.println(Thread.currentThread().getName());}
}
public class TextDemo {public static void main(String[] args){MyThread mt = new MyThread();new Thread(mt,"自定义线程").start() ;mt.run();   //主方法之中进行调用}
}

现在发现通过“mt.run()”执行的时候取得的线程名称是main,所以可知:主方法也是一个线程,而新的问题就出现了,一直在讨论线程,但线程依附于进程,进程也在那里?

        每当用户使用Java命令解释一个类的时候,对操作系统而言,都会默认启动一个Java的进程,而主方法只是这进程之中的一个线程而已。每一个JVM运行时至少要启动两个线程:主线程、GC垃圾回收线程。

2.线程休眠,线程的休眠指的是让程序暂缓执行,休眠方法:public static void sleep(long millis)throws InterruptedException;

范例:观察休眠

package cn.mldn.demo;class MyThread implements Runnable{public void run(){   //线程的主方法for(int x = 0; x<100; x++){try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName());}}
}
public class TextDemo {public static void main(String[] args){MyThread mt = new MyThread();new Thread(mt,"线程A").start() ;new Thread(mt,"线程B").start() ;new Thread(mt,"线程C").start() ;new Thread(mt,"线程D").start() ;new Thread(mt,"线程E").start() ;}
}

由于线程资源的间隔很短,那么可以简单地理解为,多个线程有可能会同时进入到方法执行。

3.线程的优先级

从理论上讲,线程的优先级越高,越有可能最先执行。但这也只是可能,如果想进行线程的优先级操作,可以使用如下的两个方法:

  • 设置优先级:public final void setPriority(int newPriority);
  • 取得优先级:public final int getPriority();

对于优先级在Thread类之中定义了三个常量:

  • 最高优先级:public static final int MAX_PRIORITY, 10;
  • 中等优先级:public static final int NORM_PRIORITY, 5;
  • 最低优先级:public static final int MIN PRIORITY, 1。

范例:观察优先级改变

package cn.mldn.demo;class MyThread implements Runnable{public void run(){   //线程的主方法for(int x = 0; x<20; x++){try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName());}}
}
public class TextDemo {public static void main(String[] args){MyThread mt = new MyThread();Thread mt1 = new Thread(mt,"线程A");Thread mt2 = new Thread(mt,"线程B");Thread mt3 = new Thread(mt,"线程C");mt1.setPriority(Thread.MIN_PRIORITY);mt3.setPriority(Thread.MAX_PRIORITY);mt1.start();mt2.start();mt3.start();}
}

 问题:那么主线程的优先级是多少呢

public class TextDemo {public static void main(String[] args){System.out.println(Thread.currentThread().getPriority());}
}

主线程只是一个中等优先级。

线程的同步与死锁

1.线程的同步

当多个线程访问同一资源的时候一定需要考虑到同步问题,那么下面首先经过一个简单的买票程序,来观察同步问题的情况。

范例:观察问题

package cn.mldn.demo;class MyThread implements Runnable{private int ticket = 8 ;public void run(){   //线程的主法for(int x = 0 ; x < 20 ; x ++ ){if(this.ticket > 0){System.out.println(Thread.currentThread().getName() + ",ticket = " + this.ticket--);}}}
}
public class TextDemo {public static void main(String[] args){MyThread mt = new MyThread();new Thread(mt,"票贩子A").start();new Thread(mt,"票贩子B").start();new Thread(mt,"票贩子C").start();new Thread(mt,"票贩子D").start();new Thread(mt,"票贩子E").start();}
}

  那么在这个时候就发生了不同步的情况,而发生的原因也很简单。对于整个卖票过程实际上是两步完成的

  • 第一步:使用if语句判断是否有票;
  • 第二步:进行票数的修改。

  但是此时有可能会出现这样一种情况:如果说现在只有最后一张票了,可以满足if条件判断,所以线程可以通过if语句拦截,可是在修改票之前出现了一个延迟操作,那么有可能在这个延迟的过程之中,又出现了其它线程进入到方法中,由于此时没有修改票数,那么这个线程也可以满足if条件判断,后面的线程依次类推,所以当休眠时间已过,进行票数修改的时候,都将在已有的票数上修改,自然就有可能出现负数。
  那么既然已经清楚了问题的产生原因,下面就必须进行问题的解决,问题的解决关键是需要一把锁。如果要想上好这把“锁”,则可以采用两种方式完成:同步代码块、同步方法。 I
范例:使用同步代码块
  同步代码块是使用synchronized关键字定义的代码块,但是在进行同步的时候一定要设置好一个同步对象,所以这个同步对象一般使用当前对象this表示。

package cn.mldn.demo;class MyThread implements Runnable{private int ticket = 8 ;public void run(){   //线程的主法for(int x = 0 ; x < 20 ; x ++ ){if(this.ticket > 0){try{Thread.sleep(10000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ",ticket = " + this.ticket--);}}}
}
public class TextDemo {public static void main(String[] args){MyThread mt = new MyThread();new Thread(mt,"票贩子A").start();new Thread(mt,"票贩子B").start();new Thread(mt,"票贩子C").start();new Thread(mt,"票贩子D").start();new Thread(mt,"票贩子E").start();}
}

  此时的程序执行速度变得明显缓慢了,因为需要一个个排队进行,就好比你们上网和去银行取钱。一定是上网快。虽然性能变慢了,但是数据的安全性提高了,也就是说异步处理(不加synchronized)和同步处理(synchronized)的使用区别也在于此。
范例:同步方法实现

如果在一个方法的声明上使用synchronized关键字,则表示此方法是一个同步方法。

package cn.mldn.demo;class MyThread implements Runnable{private int ticket = 8 ;public void run(){for(int x = 0 ; x < 20 ; x ++ ){this.sale();}}public synchronized void sale(){   //线程的主法if(this.ticket > 0){try{Thread.sleep(10000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ",ticket = " + this.ticket--);}}
}
public class TextDemo {public static void main(String[] args){MyThread mt = new MyThread();new Thread(mt,"票贩子A").start();new Thread(mt,"票贩子B").start();new Thread(mt,"票贩子C").start();new Thread(mt,"票贩子D").start();new Thread(mt,"票贩子E").start();}
}

同步的核心意义:一个线程要等另一个线程执行完毕

2.死锁

死锁实际上是在项目运行过程之中产生的一种问题,简单说就是多个线程访问同一个资源的时候一定要考虑同步,但是同步会影响程序的性能,同时会提升数据的安全性,过的同步会出现死锁

线程通讯的经典案例--生产者和消费者

本程序的核心结构如下:首先定义两个类,一个是生产者线程类,另一个是消费者线程类生产者每年生产完一个数据之后,消费者要取走这些数据,那么假设现在的数据有两种:

title=小动物,content= 草泥马;

title=小金子,content=不是好孩子。

范例

package cn.mldn.demo;class Message{private String title;private String content;public void setTitle(String title){this.title = title;}public void setContent(String content){this.content= content;}public String getContent(){return content;}public String getTitle(){return title;}
}
class Productor implements Runnable{private Message msg;public Productor(Message msg){this.msg=msg;}public void run(){for(int x = 0 ; x < 100 ; x++){if(x % 2 ==0){this.msg.setTitle("小动物");try{Thread.sleep(100);}catch(InterruptedException e){e.printStackTrace();}this.msg.setContent("草泥马");}else{this.msg.setTitle("小金子");try{Thread.sleep(100);}catch(InterruptedException e){e.printStackTrace();}this.msg.setContent("不是好孩子");}}}
}
class Custome implements Runnable{private Message msg;public Custome(Message msg){this.msg=msg;}public void run(){for(int x = 0 ; x < 100 ; x++){try{Thread.sleep(100);}catch(InterruptedException e){e.printStackTrace();}System.out.println(this.msg.getTitle() + "," + this.msg.getContent());}}}
public class TextDemo {public static void main(String[] args){Message msg = new Message ();new Thread(new Productor(msg)).start();new Thread(new Custome(msg)).start();}
}

遗憾的是,这个时候出现了两个问题,

  • 数据错乱
  • 数据重复取出与重复设置

1.解决数据错位问题

想要解决数据错位问题,使用同步处理即可,代码如下

package cn.mldn.demo;class Message{private String title;private String content;public synchronized void set(String title , String content){this.title=title;try{Thread.sleep(100);}catch(InterruptedException e){e.printStackTrace();}this.content=content;}public synchronized void get(){try{Thread.sleep(100);}catch(InterruptedException e){e.printStackTrace();}System.out.println(this.title + "," + this.content);}
}
class Productor implements Runnable{private Message msg;public Productor(Message msg){this.msg=msg;}public void run(){for(int x = 0 ; x < 100 ; x++){if(x % 2 ==0){this.msg.set("小动物","草泥马");}else{this.msg.set("小金子","不是好孩子");}}}
}
class Custome implements Runnable{private Message msg;public Custome(Message msg){this.msg=msg;}public void run(){for(int x = 0 ; x < 100 ; x++){this.msg.get();}}}
public class TextDemo {public static void main(String[] args){Message msg = new Message ();new Thread(new Productor(msg)).start();new Thread(new Custome(msg)).start();}
}

 现在数据没有错乱,但是重复操作更严重了

2.解决数据重复操作

 如果要想解决重复的操作必须加入等待与唤醒机制。而这样的处理机制是由Object类提供的方法支持,在Object类之中有如下的方法可以使用:

  • 等待:public final void wait throws InterruptedException;
  • 唤醒第一个:public final void notify();
  • 唤全部:public final void notifyAll(),谁的优先级高就先执行。

范例:解决问题

package cn.mldn.demo;class Message{private String title;private String content;private boolean flag = true;//flag = true:表示可以生产,但是不能够取走//flag = false:表示可以取走,但不能生产public synchronized void set(String title , String content){if(this.flag == false){try{super.wait();}catch(InterruptedException e){e.printStackTrace();}}this.title=title;try{Thread.sleep(100);}catch(InterruptedException e){e.printStackTrace();}this.content=content;this.flag = flag;super.notify();}public synchronized void get(){if(this.flag == true){try{super.wait();}catch(InterruptedException e){e.printStackTrace();}}try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}System.out.println(this.title + "," + this.content);this.flag = true;super.notify();}
}
class Productor implements Runnable{private Message msg;public Productor(Message msg){this.msg=msg;}public void run(){for(int x = 0 ; x < 100 ; x++){if(x % 2 ==0){this.msg.set("小动物","草泥马");}else{this.msg.set("小金子","不是好孩子");}}}
}
class Custome implements Runnable{private Message msg;public Custome(Message msg){this.msg=msg;}public void run(){for(int x = 0 ; x < 100 ; x++){this.msg.get();}}}
public class TextDemo {public static void main(String[] args){Message msg = new Message ();new Thread(new Productor(msg)).start();new Thread(new Custome(msg)).start();}
}

解释sleep()和wait()的区别

  • sleep是Thread类定义的方法,在休眠之后可以自动唤醒;。
  • wait()是Object类定义的方法,等待之后必须使用notify()或notifyAll()手工唤醒。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部