1 同步生效的条件
2 wait();notify();notifyAll();三个方法
3 多线程之间的交互通信易存在的问题(难点)
4 线程停止
5 守护线程
6 join方法
7 线程优先级
1 同步生效的条件
先看一段铺垫代码,这个代码很简单,但有2个地方需要注意。
- 多线程意味着输入和输出都需要被同步(存在多个线程);
- 注意多线程要使用相同的锁,这个锁是输入程序和输出程序共同使用的Resource对象;
public class ThreadCommunication {
public static void main(String[] args){
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
class Resource{
String name;
String sex;
boolean flag = false;
}
public class InputResource implements Runnable {
private Resource r;
InputResource(Resource r) {
this.r = r;
}
@Override
public void run() {
int x = 0;
while (true) {
synchronized (r) {
if (x == 0) {
r.name = "james";
r.sex = "man";
} else {
r.name = "丽丽";
r.sex = "女";
}
x = (x + 1) % 2;
}
}
}
}
public class OutPutResource implements Runnable{
private Resource r;
OutPutResource(Resource r){
this.r = r;
}
public void run() {
while(true){
synchronized(r){
System.out.println(r.name+"::::::::::::::"+r.sex);
}
}
}
}
注意这两点之后,程序正确,输出正常。
image.png
2 wait();notify();notifyAll();三个方法
上面虽然执行正常,但并不是希望的效果,因为应该是一次输入对应一次输出。所以这时就需要使用wait和
notify来对线程进行睡眠和唤醒操作。
代码如下:
public class InputResource_1 implements Runnable {
private Resource r;
InputResource_1(Resource r) {
this.r = r;
}
@Override
public void run() {
int x = 0;
while (true) {
synchronized (r) {
if (r.flag) {
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//首先执行false,赋值操作
if (x == 0) {
r.name = "james";
r.sex = "man";
} else {
r.name = "丽丽";
r.sex = "女";
}
x = (x + 1) % 2;
r.flag = true;//flag置为true之后,output被notify,input被wait
r.notify();
}
}
}
}
public class OutPutResource_1 implements Runnable {
private Resource r;
OutPutResource_1(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
synchronized (r) {
if (!r.flag) {
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(r.name + "::::::::::::::" + r.sex);
r.flag = false;
r.notify();
}
}
}
}
image.png
这里的只wait和notify等待和唤醒必须是同一个锁。
3 多线程之间的交互通信易存在的问题(难点)
例子的要求是:生产者生产一个,消费者就消费一个,生产和消费一一对等。
代码如下:
public class ProducerAndConsumer {
public static void main(String[] args){
ResourcesGS r = new ResourcesGS();
Producer p = new Producer(r);
Consumer c = new Consumer(r);
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
Thread t3 = new Thread(p);
Thread t4 = new Thread(p);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
public class ResourcesGS {
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name +"---"+ count++;
System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
flag = true;
this.notify();
}
public synchronized void out(){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"........消费者......."+this.name);
flag = false;
this.notify();
}
}
public class Producer implements Runnable{
private ResourcesGS r;
Producer(ResourcesGS r){
this.r = r;
}
@Override
public void run() {
while(true){
r.set("producer");
}
}
}
public class Consumer implements Runnable{
private ResourcesGS r;
Consumer(ResourcesGS r){
this.r = r;
}
@Override
public void run() {
while(true){
r.out();
}
}
}
大部分代码都是和谐状态,但是的确存在错误现象:生产一次,消费多次,生产多次,消费一次。
image.png
首先从代码来分析,主要是ResourcesGS中的代码
public class ResourcesGS {
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name +"---"+ count++;
System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
flag = true;
this.notify();
}
public synchronized void out(){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"........消费者......."+this.name);
flag = false;
this.notify();
}
}
t1,t2是生产者,t3,t4是消费者。
假设:
- t1抢到执行权,首先“生产1”,然后置标记为false,notify剩下的三个线程t2,t3,t4全部被唤醒,接着wait()等待;
- 如果这时t2抢到执行权,标记为true,wait()等待。那么就意味这等待队列中现在是t1,t2的排序。
- t3抢到执行权,执行代码"消费1",然后置标记为true,notify了t1,因为t4本来没睡,所以t4抢到执行权,标记为true,直接wait。第一次的生产消费是和谐状态。
- 这时t1抢到执行权,“生产2”,然后置标记为false,t1执行notify,这时根据队列中等待线程的排列顺序,t2被唤醒(出错点),这时t2不用判断标记,直接继续向下执行,“生产3”。这里就连续生产了2次。
这时就会导致生产两次,消费一次的现象。
那么怎么解决呢?
问题的核心是:t2没有判断标记,直接继续执行了"生产"的代码。只需要让t2一直判断标记即可。就是将if判断改为while判断。这时,即使t2醒了也需要在重新判断标记,所以可以修复改bug。
但是修改后运行结果如下:
image.png
只走了前三个线程,程序就停止了。
假设t2这时醒了,然后进行while语句的判断,这时标记为true,然后将会被wait(),直接使用object类中的notifyAll()方法唤醒所有的线程即可。这时,即使t2也睡了,但是t3和t4全部被唤醒,可以继续向下执行,程序就会继续执行了。
代码如下:
class ResourcesGS{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
while(flag){
try {
this.wait();//t1 t2
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name +"---"+ count++;
System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
flag = true;
this.notifyAll();
}
public synchronized void out(){
while(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"........消费者......."+this.name);
flag = false;
this.notifyAll();
}
}
最后的结果如下,一切都正常了!
image.png
4 线程停止
stop方法已经过时,现在常见的停止线程的方法如下:
(1)让运行的线程结束
代码如下:通过控制标记,结束run方法。
public class StopThread implements Runnable{
private boolean flag = true;
@Override
public void run() {
while (flag){
System.out.println(Thread.currentThread().getName()+"...run");
}
}
public void changeFlag(){
flag = false;
}
public static void main(String[] args) {
StopThread stopThread = new StopThread();
Thread t1 = new Thread(stopThread);
Thread t2 = new Thread(stopThread);
t1.start();
t2.start();
int num = 0;
while(true){
if(num++ == 60){
stopThread.changeFlag();
break;
}
System.out.println(Thread.currentThread().getName()+"..."+num);
}
}
}
(2)interrupt方法
该方法不是停止线程,而是中断线程。主要是会将 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int)等方法的状态清除。强制让线程恢复到运行状态中,这样就可以继续操作标记让线程结束。
public class StopThread implements Runnable{
private boolean flag = true;
@Override
public synchronized void run() {
while (flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+"...run");
flag = false;
}
}
}
public void changeFlag(){
flag = false;
}
public static void main(String[] args) {
StopThread stopThread = new StopThread();
Thread t1 = new Thread(stopThread);
Thread t2 = new Thread(stopThread);
t1.start();
t2.start();
int num = 0;
while(true){
if(num++ == 100){
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"..."+num);
}
}
}
运行结果如下:抛出异常,通过置标记,停止线程。
image.png
5 守护线程
Java中提供守护线程,特点是:守护线程属于后台线程,而主线程是前台线程,守护线程会在主线程运行结束之后自动停止。
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
public final void setDaemon(boolean on)
刚才的代码,如果将t1和t2都设置为守护线程,那么就不需要人为去设置停止,t1,t2会在主线程运行结束后自动结束。
t1.setDaemon(true);
t2.setDaemon(true);
6 join方法
当A线程执行到了B线程的join方法时,A就会等待,等B线程都执行完毕,A才会继续执行。一般join用来临时加入线程执行。
例如,若这样执行,main、Thread0、Thread1会随机无序的交替执行。
public class StopThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 280; i++) {
System.out.println(Thread.currentThread().getName()+"...run");
}
}
public static void main(String[] args) throws InterruptedException {
StopThread stopThread = new StopThread();
Thread t1 = new Thread(stopThread);
Thread t2 = new Thread(stopThread);
t1.start();
t2.start();
for (int i = 0; i < 280; i++) {
System.out.println(Thread.currentThread().getName()+"...main");
}
}
}
image.png
若在 t1.start()后面加上 t1.join()。
t1.start()
t1.join()
t2.start()
执行结果如下:
image.png
如上所述,main、Thread1(t2)会等Thread0(t1)执行完毕后在执行。
7 线程优先级
1 toString()
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
image.png
2 更改线程优先级
如上图,所有线程的默认优先级是5,但是可以根据需要,设置线程的优先级,优先级高的cpu在执行时就会优先考虑,执行的机会就更大。
提供1,5,10三个档次的优先级。
image.pnggetPriority()
setPriority(int newPriority)
多线程的基础知识总结完毕。
END
网友评论