Java篇-多线程

2018-07-08  本文已影响24人  TianTianBaby223

多线程的概念已经写过很多了,java的多线程的概念和之前讲解的 OC 与 python多线程相同,这里就不再赘述了.其基本原理也相同,只是方法有所区别.主要列举java中多线程的方法和使用.

一 : 线程创建的两种方式

通过继承Thread 进行进行线程创建

首先创建一个类继承与Thread 重写 run方法

//创建一个继承于Thread的子类
class SubThread extends Thread{
    //重写Thread的run方法,方法内部实现此线程要完成的功能
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

调用

public class TestThread {
    public static void main(String[] args) {
        //创建一个子类的对象
        SubThread st = new SubThread();
        //调用线程的start() 启动此线程;调用相应的run()方法
        st.start();
        //不能通过Thread实现类对象的run()去启动一个线程
    }
} 

创建一个实现Runnable接口的类

class TZprint implements Runnable{
    
    //实现接口的抽象方法
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println();
            }
        }
    }
}

调用

public class TestThread2 {
    public static void main(String[] args) {
        //创建一个Runnable接口实现类对象
        TZprint p = new TZprint();
        //将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,
        //此对象即为一个线程
        Thread t = new Thread(p);
        //调用start()方法,启动线程并执行run()
        t.start();//启动线程,执行Thread对象生成时构造器形参的run方法
    }
}

二 : 线程中常用方法

创建一个继承于Thread的子类

class SubThread1 extends Thread {
    // 2.重写Thread的run方法,方法内部实现此线程要完成的功能
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + i + Thread.currentThread().getPriority());
        }
    }

}

线程常用的方法

    // 创建一个子类的对象
        SubThread1 st = new SubThread1();
        // 调用线程的start() 启动此线程;调用相应的run()方法
        st.start();
for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i + Thread.currentThread().getPriority());
}
SubThread1 st = new SubThread1();
        st.setName("test");
Thread.currentThread().yield();
SubThread1 st = new SubThread1();
        st.start();
for (int i = 0; i < 100; i++) {
             if (i == 20) {
             try {
             st.join();
             } catch (InterruptedException e) {
             TODO Auto-generated catch block
             e.printStackTrace();
             }
            }
}
SubThread1 st = new SubThread1();
        st.start();
 System.out.println(st.isAlive());

三 : 线程同步

常见的线程安全问题
实例-模拟火车站售票窗口,开启三个窗口售票,总票数为100张

class Window extends Thread {
    static int ticket = 100;

    public void run() {
        while (true) {
            if (ticket > 0) {
//              try {
//                  Thread.currentThread().sleep(10);
//              } catch (InterruptedException e) {
//                  // TODO Auto-generated catch block
//                  e.printStackTrace();
//              }
System.out.println(Thread.currentThread().getName() + "售票,票号为:"
                        + ticket--);
            } else {
                break;
            }
        }
    }
}

调用

public class TestWindow {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
        
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");
        
        w1.start();
        w2.start();
        w3.start();
        
    }
}

分析 : 由于一个线程在操作共享数据过程中,未执行完毕,另外的线程参与进来,进入方法run 并且同时进入条件 ticket > 0 会造成错票现象,

解决办法 : 线程同步 的两种方式

必须让一个线程操作共享数据完毕以后,其它线程才有机会参与共享数据的操作。

synchronized(同步监视器){
        //需要被同步的代码块(即为操作共享数据的代码)
        }

1.共享数据:多个线程共同操作的同一个数据(变量)
2.同步监视器:由一个类的对象来充当。哪个线程获取此监视器,谁就执行大括号里被同步的代码。俗称:锁
要求:所有的线程必须共用同一把锁!


优化购票
class Window implements Runnable {
    int ticket = 100;// 共享数据
//  Object obj = new Object();
    public void run() {
//      Animal a = new Animal();//局部变量
        while (true) {
            synchronized (this) {//this表示当前对象,本题中即为w
                if (ticket > 0) {
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "售票,票号为:" + ticket--);
                }
            }
        }
    }
}

public class TestWindow2 {
    public static void main(String[] args) {
        Window w = new Window();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start()
    }
}

注:在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this!

错误示范

解析 : 在此错误示例中,同步代码块中的锁,this 是 当前实例后的对象,相当于给每个 窗口 创建了一把锁,此时并不能起到互相约束的作用,此锁就变得没有意义.

class Window extends Thread {
    static int ticket = 100;
    static Object obj = new Object();

    public void run() {
        while (true) {
            synchronized (this) {//在本问题中,this表示:w1,w2,w3
                if (ticket > 0) {
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "售票,票号为:" + ticket--);
                }
            }
        }
    }
}
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();

    }

}
改正 : 把继承Threadwindow 中的同步代码块中的

this 换成obj

将操作共享数据的方法声明为synchronized。即此方法为同步方法,能够保证当其中一个线程执行
此方法时,其它线程在外等待直至此线程执行完此方法。
同步方法的锁:this

lass Window implements Runnable {
    int ticket = 100;// 共享数据

    public void run() {
        while (true) {
            show();
        }
    }

    public synchronized void show() {
        if (ticket > 0) {
            try {
                Thread.currentThread().sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售票,票号为:"
                    + ticket--);
        }

    }
}
public class TestWindow4 {
    public static void main(String[] args) {
        Window w = new Window();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

注意 : 同步方法里面的锁默认是 this 所以对于继承方式的线程起不到同步的作用.

线程的同步的弊端:由于同一个时间只能有一个线程访问共享数据,效率变低了,但这并能阻止我们去在必要的时候保持线程的安全性.

四 : 死锁

死锁的问题:处理线程同步时容易出现,不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

示例分析

public class TestDeadLock {
    static StringBuffer sb1 = new StringBuffer();
    static StringBuffer sb2 = new StringBuffer();

    public static void main(String[] args) {
//线程A
        new Thread() {
            public void run() {
                synchronized (sb1) {
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    sb1.append("A");
                    synchronized (sb2) {
                        sb2.append("B");
                        System.out.println(sb1);
                        System.out.println(sb2);
                    }
                }
            }
        }.start();

//线程B
        new Thread() {
            public void run() {
                synchronized (sb2) {
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    sb1.append("C");
                    synchronized (sb1) {
                        sb2.append("D");
                        System.out.println(sb1);
                        System.out.println(sb2);
                    }
                }
            }
        }.start();
    }

}

分析 :

在执行线程A (看代码注释)过程中 首先 sb1 为锁 执行同步代码块->sb1.append("A");,拼接"A"后又以'sb2'为锁 执行sb2.append("B"); System.out.println(sb1); System.out.println(sb2);

同理在执行线程B先以 sb2为锁头,再以sb1为锁头 ,当然此时A 线程和B线程相当于同时进行,虽有先后顺序.

在A线程中以sb2为锁头时 要等线程B用完,但要先执行完线程B 释放'sb2'这把锁,必须要等线程A中sb1这把锁头用完,但是要想释放sb1必须要等同步方法中以'sb2'为锁头时的同步方法执行完...无线循环.这就是死锁.

五 : 线程通信

关键字

示例

使用两个线程打印 1-100. 线程1, 线程2 交替打印

class PrintNum implements Runnable {
    int num = 1;
    Object obj = new Object();
    public void run() {
        while (true) {
            synchronized (obj) {
                obj.notify();
                if (num <= 100) {
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":"
                            + num);
                    num++;
                } else {
                    break;
                }
                
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

}

public class TestCommunication {
    public static void main(String[] args) {
        PrintNum p = new PrintNum();
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(p);
        
        t1.setName("甲");
        t2.setName("乙");
        
        t1.start();
        t2.start();
    }
}
上一篇 下一篇

猜你喜欢

热点阅读