Thinking in java:多线程

关于多线程,看了许多大佬的帖子,自己也思索了很久,虽然许多地方还是不清楚,但还是有必要记录一下自己的所得。

首先贴上状态图:

(1)New:创建一个线程时,线程进入这个状态

(2)Runnable:调用start()后,进入这个状态

(3)Running:执行run()时,进入这个状态

(4)Blocked:阻塞状态,分3种情况

  • 等待阻塞:调用wait()后进入等待阻塞。
  • 同步阻塞:当多个线程同时执行某一对象的同步代码块时,只有一个线程能执行,其余线程进入阻塞状态。
  • 其他阻塞:调用sleep进入"睡眠“,join()另一个线程先执行,或有I/O请求进入其他阻塞。

一.建立线程的3种形式

(1)直接继承Thread创建

public class Test
{
    public static void main(String[] args)
    {
        MyThread mt=new MyThread();
        mt.start();
    }
}
class MyThread extends Thread
{
    @Override
    public void run() {
        System.out.println("这是一个新线程");
    }
}

(2)实现Runnable接口

public class Test
{
    public static void main(String[] args)
    {
        MyThread mt=new MyThread();
        Thread t=new Thread(mt);
        t.start();
    }
}
class MyThread implements Runnable
{
    @Override
    public void run() {
        System.out.println("这是一个新线程");
    }
}

(3)实现Callable接口,用FutureTask进行封装

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test
{
    public static void main(String[] args)
    {
        Callable mt=new MyThread();
        FutureTask ft=new FutureTask(mt);
        Thread t=new Thread(ft);
        t.start();
        try {
            System.out.println(ft.get());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
class MyThread implements Callable
{
    @Override
    public String call() {
        System.out.println("这是一个新线程");
        return "测试结束";
    }
}

在这种实现方式中,线程可以具备返回值。

二.线程的三种阻塞状态

在将阻塞之前,有必要了解一下synchronized关键字的意思,用synchronized修饰的代码块代表着线程同步,假如一个类A有3个同步方法,有一对象a是A的实例,如果有一线程正在执行对象a的某一个同步方法,那么其余任何线程都不能执行对象a的同步方法。专业点说就是当前执行的线程获得了对象a的"同步锁",其他线程要去执行a的同步方法必须先去获得"同步锁",但发现”同步锁“没有释放,因此只能等着当前执行的线程执行完并且释放”同步锁“。

(1)sleep()阻塞,join()阻塞和I/O阻塞

sleep()方法使线程进入"睡眠"状态,但在睡眠状态中不会释放同步锁,示例:

public class Test
{
    public static void main(String[] args) throws InterruptedException
    {
        int i=0;
        ClassTest ct=new ClassTest();
        ThreadOne t1=new ThreadOne(ct);
        Thread.sleep(1000);
        i++;
        ThreadTwo t2=new ThreadTwo(ct);
        while (true)
        {
            Thread.sleep(1000);
            System.out.println(++i);
        }
    }
}
class ThreadOne extends Thread
{
    public ClassTest ct;
    public ThreadOne(ClassTest ct)
    {
        this.ct=ct;
        start();
    }
    @Override
    public void run() {
        try {
            ct.printOne();
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class ThreadTwo extends Thread
{
    public ClassTest ct;
    public ThreadTwo(ClassTest ct)
    {
        this.ct=ct;
        start();
    }

    @Override
    public void run() {
        ct.printTwo();
    }
}
class ClassTest
{
    public synchronized void printOne() throws InterruptedException
    {
        Thread.sleep(5000);
        System.out.println("--------");
    }
    public synchronized void printTwo()
    {
        System.out.println("++++++++");
    }
}

输出如下:

在这个代码中,用两个线程t1和t2去分别运行ct对象的printOne()和printTwo()方法,t2比t1后1秒运行,结果是在第5秒时,两个依次输出,在printTwo()中,没有设置线程"睡眠"但依旧等到printOne()输出后才输出,证明sleep()不会释放同步锁。

join()方法的作用是等待另一线程执行完,在执行自身线程。示例:

public class Test
{
    public static void main(String[] args) throws InterruptedException
    {
        ThreadOne t1=new ThreadOne();
        t1.start();
        t1.join();
        while (true)
        {
            System.out.println(0);
        }
    }
}
class ThreadOne extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + i);
        }
    }
}

输出如下:

截图不能截完,自行测试,当线程t1join()之后,直到t1执行完才开始执行主线程,主线程阻塞,相当于t1线程与主线程变成了顺序执行。

I/O阻塞直接看示例

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class Test
{
    public static void main(String[] args)
    {
        try {
            PipedInputStream in = new PipedInputStream();
            PipedOutputStream out = new PipedOutputStream(in);
            ThreadIn read = new ThreadIn(in);
            ThreadOut write = new ThreadOut(out);
            write.start();
            read.start();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
class ThreadIn extends Thread
{
    PipedInputStream in;
    public ThreadIn(PipedInputStream in)
    {
        this.in=in;
    }

    @Override
    public void run() {
        try {
            while (true) {
                in.read(new byte[1024]);
                System.out.println("读一次");
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
class ThreadOut extends Thread
{
    PipedOutputStream out;
    public ThreadOut(PipedOutputStream out)
    {
        this.out=out;
    }

    @Override
    public void run() {
        try {
            while (true)
            {
                out.write("abcde".getBytes());
                sleep(2000);
                System.out.println("等待2秒");
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

输出如下:

"读线程"每次都要等"写线程"写入数据之后才读取,当没有数据读取的时候就阻塞。

(2)等待阻塞与同步阻塞

等待阻塞是由wait()引发的,wait()的作用是将当前线程挂起,和sleep()作用相似,但wait()会释放同步锁,当wait()挂起时,其余线程也可以获得当前对象的”同步锁“,notify和notifyAll可以解除由wait()挂起的线程,它们都必须存在于synchronized块中,且必须作用于同一对象。示例如下:

import java.io.IOException;

public class Test
{
    public static void main(String[] args)
    {
        try {
            ClassTest ct = new ClassTest();
            ThreadOne t1 = new ThreadOne(ct);
            ThreadTwo t2 = new ThreadTwo(ct);
            t1.start();
            Thread.sleep(1000);
            t2.start();
            while (true)
            {
                int ch=System.in.read();
                if(ch=='a')
                {
                    System.out.println("按下A键,改变ct.blag=true");
                    ct.blag=true;
                }
                if(ch=='b')
                {
                    System.out.println("按下B键,改变ct.blag=false");
                    ct.blag=false;
                    Thread.sleep(1000);
                    System.out.println(ct.blag);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
class ThreadOne extends Thread
{
    ClassTest ct;
    public ThreadOne(ClassTest ct)
    {
        this.ct=ct;
    }

    @Override
    public void run() {
        ct.print();
    }
}
class ThreadTwo extends Thread
{
    ClassTest ct;
    public ThreadTwo(ClassTest ct)
    {
        this.ct=ct;
    }

    @Override
    public void run() {
        ct.waitPrint();
    }
}
class ClassTest
{
    volatile boolean blag=false;
    public synchronized void print()
    {
        int i=0;
        System.out.println("print占用");
        try {
            while (true) {
                while (!blag) {
                    Thread.sleep(3000);
                    System.out.println(i++);
                }
                System.out.println("print释放同步锁");
                wait();
                System.out.println("结束wait");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public synchronized void waitPrint()
    {
        System.out.println("waitPrint获得同步锁");
        while (blag) {}
        System.out.println("waitPrint结束");
        notify();
    }
}

输出如下:

在这个测试用例里面,t1线程最开始获得同步锁,并每隔3秒输出一个i值,t2线程阻塞,等待t1执行完,主线程改变blag状态使得t1线程和t2线程可以释放同步锁。等待阻塞就是当线程调用wait方法后,自身会"挂起",进入阻塞状态,但会释放同步锁,此例中t1在"挂起"后t2可以调用ct对象的同步方法。同步阻塞即多个线程同时调用一个对象的同步方法,只有一个线程能执行,此例中t1先调用print,t2只有等t1释放同步锁后才能执行。

注意:blag必须声明为volatile,每个线程都有自己的缓存(线程栈),它们从主存拿取数据,如果不声明为volatile,即便主线程改变了blag的值,t2线程使用的也可能时自己线程本身缓存里面的blag值。

猜你喜欢

转载自blog.csdn.net/qq_27368993/article/details/82706689