关于多线程,看了许多大佬的帖子,自己也思索了很久,虽然许多地方还是不清楚,但还是有必要记录一下自己的所得。
首先贴上状态图:
(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值。