线程的概念
一个程序中不同的执行路径,可以放在不同的CPU里边同步执行的
如何启动一个线程
- 一、继承Thread类 (Java.lang.Thread)
Java.lang.Thread 是应用程序层面对Java线程的抽象类。也就是说,一个Thread类代表一个Java线程。
可以通过重写Thread的run()
方法规定线程要完成的任务。
可以通过调用 Thread的start()
方法启动一个线程。
示例:
public class Main {
public static void main(String[] args) {
new TestThread().start();
System.out.println("Main ");
}
}
class TestThread extends Thread{
@Override
public void run() {
try {
//Old style
//Thread.sleep(1000);
//让线程暂停一秒,被注释的是JDK 1.5之前的写法。
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread is running");
}
}
运行结果:
Main
thread is running
可以看到,thread is running
在程序运行一秒后被打印在控制台上,两个线程(main
线程 和 TestThread
创建的线程)没有相互影响。
注意:
通过调用 Thread
的 start()
方法启动一个线程。而不是run()
方法,调用run()
方法如同普通类的public
方法调用。
- 二、实现Runnable接口(Java.lang.Runnable)
通过Thread启动线程并不是一种理想的方式,缺点如下:
由于Java的单继承限制,继承Thread类阻止了我们继承其他类。
更加符合面向对象的规则,Runnable被视作“任务”,Thread则是Java程序中实现并发的“工具”。
我们可以通过Thead类的构造方法:
Thread(Runnable runnable)
构建一个线程。
上一段代码的等效代码:
public class Main {
public static void main(String[] args) {
Runnable myTask = new TestTask();
new Thread(myTask).start();
System.out.println("Main ");
}
}
class TestTask implements Runnable{
@Override
public void run() {
try {
//Old style
//Thread.sleep(1000);
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread is running");
}
}
- 三、匿名内部类:
匿名内部类本质上也是一个类实现了Runnable
接口,重写了run()
方法,只不过这个类没有名字,直接作为参数传入Thread
类,示例代码:
Main类:
public class Main {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "线程执行" + i);
}
}
}).start();
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "线程执行" + i);
}
}
}
Further Questions:
- Thread 类自身维护了一个 对于其本身的引用 所以直到
run()
方法执行完成,Thread实例不会被垃圾回收器回收。 run()
方法无法向外抛出任何异常,所以我们应该认真设计线程中的异常处理、流程控制、线程结束后的资源处理。- 由于多个线程同时运行,两个线程“同时”访问或修改同一资源的事件可能时常发生,我们需要极其认真处理这类事件。(synchronized volatile以及lock)
严格来说,单处理器机器上,两个线程不可能“同时”访问一个资源。然而在线程看来,每个线程都是在独享CPU,这种方式的底层实现是CPU时间切片,我们永远无法确定线程在何使被“挂起”,在何时运行。
基本的线程同步:synchronized
synchronized 对某个对象加锁
`synchronized`锁的是对象而不是下边的代码块。一个`synchronized`相当于一个原子操作,原子是不可分的,
在线程执行这个代码块的时候是不肯能被打断的,执行完成之后其他线程才能够继续执行同一块代码。
- 示例一:
public class T{
private int count = 10;
//o对象创建在堆内存中
private Object o = new Object();
public void m() {
//任何线程执行下边的代码都要拿到o这把锁,该o对象是从堆内存中取出的,不是对象的引用
synchronized (o) {
/*
*如果第一个线程运行到此处,第二个线程进入的话,需要第一个线程释放o
*这个锁对象,第二个线程才可以执行;
*只要有一个线程拿到了这把锁其他线程就拿不到了,这样的锁叫互斥锁。
*/
count--;
System.out.println(Thread.currentThread().getName() + "count = " + count);
}
}
}
- 示例二:
public class T {
private int count = 10;
public void m() {
//任何线程执行下边的代码都要拿到this这把锁
synchronized (this) {
/*
*使用this对象作为锁的话,new这个对象的时候会得到这把锁
*/
count--;
System.out.println(Thread.currentThread().getName() + "count = " + count);
}
}
}
public class T {
private int count = 10;
/**
*如果一段代码在开始的时候就使用了synchronized (this)直到结束,
*这个时候synchronized 可以直接写在方法的声明上,
*等同于synchronized (this)的写法。
*/
public synchronized void m() {
//这样的写法不是锁定当前的代码,而是要执行代码的时候锁定当前对象
count--;
System.out.println(Thread.currentThread().getName() + "count = " + count);
}
}
- 示例三
public class M {
private static int count = 10;
/**
* 如果用在静态方法上相当于锁定的是M.class这个类的对象
* 静态的方法静态的属性是不需要new出对象就可以访问的,所以这里没有this
* */
public synchronized static void m() {
//这里等同于synchronized (M.class)
count--;
System.out.println(Thread.currentThread().getName() + "count = " + count);
}
public static void n() {
synchronized (T.class) {
count--;
System.out.println(Thread.currentThread().getName() + "count = " + count);
}
}
}
- 示例四
public class T implements Runnable {
private static int count = 10;
@Override
public /*synchronized*/ void run() {
//线程重入问题 第一个线程方法未执行完毕 第二个线程进入 他们在堆内存中公用的是一个T对象
count--;
System.out.println(Thread.currentThread().getName() + "count = " + count);
}
public static void main(String[] args) {
T t = new T();
for (int i = 0; i < 5; i++) {
new Thread(t, "THREAD" + i).start();
}
}
}
运行结果:
THREAD0count = 9
THREAD3count = 7
THREAD1count = 8
THREAD4count = 7
THREAD2count = 7
如果想得到运行结果是正确顺序的结果的话 在方法执行的时候加把锁。/*synchronized*/
加在方法上边。
每次运行结果:
THREAD0count = 9
THREAD1count = 8
THREAD2count = 7
THREAD4count = 6
THREAD3count = 5
- 同步方法和非同步方法能否同时执行?
public class Sync {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + "m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m1 end...");
}
public void m2() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m2 ...");
}
public static void main(String[] args) {
Sync t = new Sync();
new Thread(() -> t.m1(), "t1:").start();
new Thread(() -> t.m2(), "t2:").start();
/*new Thread(t::m1, "t1").start();
new Thread(t::m2, "t2").start();*/
}
}
运行结果:在同步(synchronized
)方法在执行的过程中非同步方法是可以执行的。只有synchronized方法在执行的过程中需要申请这把锁,而别的方法是不需要申请的。
t1:m1 start...
t2:m2 ...
t1:m1 end...