尚学堂马士兵java多线程部分笔记

一、线程进程的基本概念

略……

二、线程调用方式

1、继承Thread类
public class Test {
    public static void main(String[] args) {

        // 执行多线程
        MyThread thread = new MyThread();
        thread.start();

        // 执行主线程
        for (int i = 0; i < 500; i++) {
            System.out.println("我在执行主线程...");
        }
    }
}

class MyThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("我在执行多线程...");
        }
    }
}
2、实现Runnable接口
public class Test {
    public static void main(String[] args) {

        // 执行多线程
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread);
        thread.start();

        // 执行主线程
        for (int i = 0; i < 500; i++) {
            System.out.println("我在执行主线程...");
        }

    }
}

class MyThread implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("我在执行多线程...");
        }
    }
}

二、基本方法

1、sleep方法:使得当前线程休眠
2、join方法 :合并某个线程
public class TestJoin {

    public static void main(String[] args) {
        MyThread2 t1 = new MyThread2("线程t1");
        t1.start();
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < 10; i++) {
            System.out.println("我是主线程");
        }
    }
}

class MyThread2 extends Thread {
    MyThread2(String s) {
        super(s);
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("我是" + getName());
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
}


尚学堂马士兵java多线程部分笔记_第1张图片

3、yield方法:让出CPU,给其他线程执行的机会,当然只会让出一小会的时间
public class Test {

    public static void main(String[] args) {
        MyThread3 t1 = new MyThread3("线程t1");
        MyThread3 t2 = new MyThread3("线程t2");
        t1.start();
        t2.start();
    }
}

class MyThread3 extends Thread {
    MyThread3(String s) {
        super(s);
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + ": " + i);
            // 逢10的倍数,让给另一个线程执行
            // 可能因为计算机的性能问题有些不准确
            if (i % 10 == 0){
                yield();
            }
        }
    }
    
}

4、线程优先级:默认统一为5
public class Test {
    public static void main(String[] args) {

        Thread t1 = new Thread(new T1());
        Thread t2 = new Thread(new T2());
        t1.setPriority(Thread.NORM_PRIORITY + 3);   // 将t1的默认优先级5提高3

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

class T1 implements Runnable {

    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("T1: " + i);
        }
    }
}

class T2 implements Runnable {

    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("-----T2: " + i);
        }
    }
}

三、线程同步

1.synchronized关键字

先看以下的测试用例:

public class Test implements Runnable{

    Timer timer = new Timer();

    public static void main(String[] args) {
        Test test = new Test();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);

        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        timer.add(Thread.currentThread().getName());
    }
}

class Timer {
    private static int num = 0;

    public void add(String name) {
        num ++;
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + ",你是第" + num + "个使用timer的线程");
    }
}

我们预期是希望输出:

t1,你是第1个使用timer的线程
t2,你是第2个使用timer的线程

可是运行的结果却是:
尚学堂马士兵java多线程部分笔记_第2张图片
原因在于线程t1执行对num增至1时,CPU可能去执行线程t2,使得此时num增至2,那么线程t1再次执行时,就有可能输出2(注意:我这里使用了sleep,是为了增强效果,确保t1 在执行的过程中被t2给打断,即便不使用sleep,线程t1也有可能会输出“第二个使用timer的线程”)

所以,我们可以使用synchronized关键字对其加锁

public class Test implements Runnable{

    Timer timer = new Timer();

    public static void main(String[] args) {
        Test test = new Test();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);

        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        timer.add(Thread.currentThread().getName());
    }
}

class Timer {
    private static int num = 0;

    public synchronized void add(String name) {
        num ++;
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + ",你是第" + num + "个使用timer的线程");
    }
}

使用了synchronized,在执行方法时就会锁定当前对象(在执行这个方法时,当前对象被锁定),如果另一个对象也想使用这个方法,只能等待!

2、死锁
public class Test implements Runnable{

    public int flag = 1;
    static Object obj1 = new Object(), obj2 = new Object();

    @Override
    public void run() {
        System.out.println("flag=" + flag);
        if (flag == 1) {    // obj1的线程从此处进入
            synchronized (obj1) {   // 锁住obj1
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj2) {   // 只要再能锁住obj2,就打印 1
                    System.out.println("1");
                }
            }

        }

        if (flag == 0) {    // obj2的线程从此处进入
            synchronized (obj2) {   // 锁住obj2
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (obj1) {   // 只要再能锁住obj1,就打印 0
                    System.out.println("0");
                }
            }

        }
    }

    public static void main(String[] args) {
        Test test1 = new Test();
        Test test2 = new Test();
        test1.flag = 1;
        test2.flag = 0;
        Thread t1 = new Thread(test1);
        Thread t2 = new Thread(test2);
        t1.start();
        t2.start();
    }
}

可以看到,程序在打印了下面两行后,便锁住了无法继续执行下去,原因就是当obj1线程执行了锁住自己的操作后,睡眠了0.5秒,此时obj2执行锁住自己操作;那么之后两个线程就进入了死锁状态,无法继续执行~(一开始也有可能是obj2先执行锁住自身操作)
尚学堂马士兵java多线程部分笔记_第3张图片

3、例子程序1
public class Test implements Runnable{
    int b = 100;

    public synchronized void m1() throws InterruptedException {
        b = 1000;
        Thread.sleep(5000);
        System.out.println("b = " + b);
    }

    public void m2() {
        System.out.println(b);
    }

    @Override
    public void run() {
        try {
            m1();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        Thread t = new Thread(test);

        t.start();
        Thread.sleep(1000);
        test.m2();
    }
}

运行结果:
尚学堂马士兵java多线程部分笔记_第4张图片
说明synchronized只是锁住了当前的方法,而另一个线程完全可以访问其他的方法,所以第一个输出的是1000,而不是100
即synchronized只能保证在同一时间,只能有一个线程访问这个被锁住的方法,但是无法影响另一个线程能否执行

4、例子程序2
public class Test implements Runnable{
    int b = 100;

    public synchronized void m1() throws InterruptedException {
        b = 1000;
        Thread.sleep(5000);
        System.out.println("b = " + b);
    }

    public void m2() throws InterruptedException {
        System.out.println(b);
        Thread.sleep(2500);
        b = 2000;
    }

    @Override
    public void run() {
        try {
            m1();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        Thread t = new Thread(test);

        t.start();
        test.m2();	// 方法调用
    }
}

运行结果:
从结果看出最后输出的是2000,而不是1000;同样说明了synchronized的特性,另一方面看出b是一种资源,凡是需要访问b这个资源的方法都必须要考虑同步锁的问题,否则会产生冲突,得到错误的结果
尚学堂马士兵java多线程部分笔记_第5张图片
如果为 m2 方法也加上synchronized,输出结果如下:

100
b = 1000

两个方法都加锁的情况下,只有当一个方法不再为锁住状态时,另一个方法才能执行。

四、生产者消费者问题

注意使用while循环而不是if判断的点
当醒来的一瞬间可能栈又满了,所以while判断更保险

wait: Object类的方法,wait的时候,会放开这把锁
sleep: Thread类的方法,sleep的时候,并不会放开锁
notify/notifyAll: 把正在等待的线程唤醒,这是必须的操作,不同于sleep会自己醒来

public class ProducerConsumer {

    public static void main(String[] args) {
        SyncStack ss = new SyncStack();
        Producer p = new Producer(ss);
        Consumer c = new Consumer(ss);
        new Thread(p).start();
        new Thread(c).start();
    }
}

class Wotou {
    int id;

    public Wotou(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Wotou{" +
                "id=" + id +
                '}';
    }
}

class SyncStack {
    int index = 0;
    Wotou[] arrWT = new Wotou[6];

    public synchronized void push(Wotou wt) {
        while (index == arrWT.length) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();  // 叫醒一个正在当前对象等待的线程
        arrWT[index] = wt;
        index ++;
    }

    public synchronized Wotou pop() {
        while (index == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();
        index --;
        return arrWT[index];
    }
}

class Producer implements Runnable {

    SyncStack ss = null;

    public Producer(SyncStack ss) {
        this.ss = ss;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            Wotou wt = new Wotou(i);
            ss.push(wt);
            System.out.println("生产了:" + wt);
            try {
                Thread.sleep((int) (Math.random() * 200));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable {

    SyncStack ss = null;

    public Consumer(SyncStack ss) {
        this.ss = ss;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            Wotou wt = ss.pop();
            System.out.println("消费了" + wt);
            try {
                Thread.sleep((int) (Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

你可能感兴趣的:(java)