概述
- 进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是每一个执行路径,或者叫一个控制单元
- 线程:进程中的一个独立的控制单元,线程在控制着进程的执行。一个进程中至少有一个线程。
创建线程
创建线程有两种方法
1. 继承Thread类
创建流程:
1.1、定义类继承Thread
1.2、复写Thread类中的run方法
1.3、调用线程的start方法(启动线程,调用run方法)
start方法开启线程并调用run方法,直接调用run方法没有开启线程(仅仅是调用对象方法)
2. 实现Runnable接口
创建流程:
2.1、定义类实现Runnable接口
2.2、覆盖Runnable接口中的run方法
2.3、通过Thread类建立线程对象
2.4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
2.5、调用Thread类的start方法开启线程
注:其中stop();方法已过时
实现方式的好处:
实现:避免单继承的局限性。独立资源。在定义线程时,建议使用实现方式
同步
- 多线程同步(synchronized)前提:
必须要有两个或者两个以上的线程
必须是多个线程使用同一个对象的锁 - 同步的利弊
同步好处:解决了多线程的安全问题
同步弊端:多个线程需要判断锁,比较消耗资源 - 函数函数的锁(this)
public class ThisLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
Thread t3 = new Thread(td);
Thread t4 = new Thread(td);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketDemo implements Runnable{
private int tick = 100;
@Override
public void run() {
while (true) {
saleTick();//这里是有省略this的
}
}
public synchronized void saleTick() {//可以推断出这里也是this锁
if (tick > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 卖票啦 " + tick--);
}
}
}
验证this锁
public class ThisLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
td.flag = false;
t2.start();
}
}
class TicketDemo implements Runnable{
private int tick = 10;
public boolean flag = true;
Object obj = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized(obj) {
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " if卖票啦 " + tick--);
}
}
}
} else {
while (true)
saleTick();
}
}
public synchronized void saleTick() {//this
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " else卖票啦 " + tick--);
}
}
}
输出结果为:
Thread-0 if卖票啦 10
Thread-1 else卖票啦 9
Thread-0 if卖票啦 8
Thread-1 else卖票啦 7
Thread-0 if卖票啦 6
Thread-1 else卖票啦 5
Thread-0 if卖票啦 4
Thread-1 else卖票啦 3
Thread-0 if卖票啦 2
Thread-1 else卖票啦 1
Thread-0 if卖票啦 0
结果出现0号票,出现安全隐患。
分析:此多线程程序加了锁还出现安全隐患,说明同步的两个前提没有满足。
第一个条件是必须两个或者两个以上的线程,此条件满足。
既然满足了第一个条件,那么肯定是第二个条件不满足了(使用同一个锁)。
来验证下this锁会不会出现安全问题:
public class ThisLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
td.flag = false;
t2.start();
}
}
class TicketDemo implements Runnable{
private int tick = 10;
public boolean flag = true;
//Object obj = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized(this) {
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " if卖票啦 " + tick--);
}
}
}
} else {
while (true)
saleTick();
}
}
public synchronized void saleTick() {//this
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " else卖票啦 " + tick--);
}
}
}
输出结果:
Thread-0 if卖票啦 10
Thread-0 if卖票啦 9
Thread-0 if卖票啦 8
Thread-0 if卖票啦 7
Thread-0 if卖票啦 6
Thread-0 if卖票啦 5
Thread-0 if卖票啦 4
Thread-1 else卖票啦 3
Thread-1 else卖票啦 2
Thread-0 if卖票啦 1
数次运行,结果并没有出现0号票,说明同步函数的锁是this
4.静态同步函数的锁是class对象
静态方法中是没有this的,那么锁是谁呢?来验证下
public class ThisLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
td.flag = false;
t2.start();
}
}
class TicketDemo implements Runnable{
private static int tick = 10;
public boolean flag = true;
@Override
public void run() {
if (flag) {
while (true) {
synchronized(this) {//这里锁是obj(new Object())输出的结果同样存在安全隐患
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " if卖票啦 " + tick--);
}
}
}
} else {
while (true)
saleTick();
}
}
public static synchronized void saleTick() {//this
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " else卖票啦 " + tick--);
}
}
}
输出结果:
Thread-0 if卖票啦 10
Thread-1 else卖票啦 9
Thread-0 if卖票啦 8
Thread-1 else卖票啦 7
Thread-0 if卖票啦 6
Thread-1 else卖票啦 5
Thread-0 if卖票啦 4
Thread-1 else卖票啦 3
Thread-0 if卖票啦 2
Thread-1 else卖票啦 1
Thread-0 if卖票啦 0
很显然,锁不是this,静态方法进内存的时候是没有对象的,是由类直接调用的,类进内存会封装成class类型的对象(即字节码文件对象):类名.class 该对象的类型是Class
那么试试:类名.class锁实验下
public class ThisLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
td.flag = false;
t2.start();
}
}
class TicketDemo implements Runnable{
private static int tick = 10;
public boolean flag = true;
@Override
public void run() {
if (flag) {
while (true) {
synchronized(TicketDemo.class) {
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " if卖票啦 " + tick--);
}
}
}
} else {
while (true)
saleTick();
}
}
public static synchronized void saleTick() {//this
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " else卖票啦 " + tick--);
}
}
}
输出结果:
Thread-0 if卖票啦 10
Thread-0 if卖票啦 9
Thread-0 if卖票啦 8
Thread-0 if卖票啦 7
Thread-0 if卖票啦 6
Thread-1 else卖票啦 5
Thread-1 else卖票啦 4
Thread-1 else卖票啦 3
Thread-1 else卖票啦 2
Thread-1 else卖票啦 1
由此可以推断出静态函数的锁是类进内存会封装成class类型的对象(即字节码文件对象):类名.class
多线程下的延迟加载单例模式(双重判断锁)
class Single {
private static Single s = null;
public static Single getInstance() {
if (s == null) {
synchronized (Single.class) {
if (s==null) {
s = new Single();
}
}
}
return s;
}
}
线程死锁:同步中嵌套同步,但是锁却不同步,容易导致死锁。线程死锁时,第一个线程等待第二个线程释放资源,而同时第二个线程又在等待第一个线程释放资源。这里举一个通俗的例子:如在人行道上两个人迎面相遇,为了给对方让道,两人同时向一侧迈出一步,双方无法通过,又同时向另一侧迈出一步,这样还是无法通过。假设这种情况一直持续下去,这样就会发生死锁现象。 导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。
来看个小例子
public class DeadLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t = new Thread(td);
Thread t1 = new Thread(td);
t.start();
try{Thread.sleep(10);}catch(Exception e){}
td.flag = false;
t1.start();
}
}
class TicketDemo implements Runnable {
private int tick = 1000;
Object obj = new Object();
boolean flag = true;
@Override
public void run() {
if(flag) {
while (true) {
synchronized (obj) {//object锁
saleTick();//this锁
}
}
} else {
while (true){
saleTick();
}
}
}
public synchronized void saleTick() {//this锁
synchronized (obj) {//object锁
if (tick > 0) {
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName() + " 线程名称 " + tick--);
}
}
}
}
输出结果(每次运行结果不一样,如果数值较小容易出现和谐状态,可以将数值调大,即可产生死锁)
再举个简单点的死锁例子:
public class DeadLockDemo2 {
public static void main(String[] args) {
Thread t = new Thread(new DeadLock(true));
Thread t1 = new Thread(new DeadLock(false));
t.start();
t1.start();
}
}
class DeadLock implements Runnable {
private boolean flag;
public DeadLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
while (true) {//如果你电脑上运行时和谐情况比较多,
//就加个循环,只要数据够大,在这种嵌套锁(锁对象不同)的情况下肯定会死锁
synchronized (MyLock.locka) {
System.out.println("if locka");
synchronized (MyLock.lockb) {
System.out.println("if lockb");
}
}
}
} else {
while (true) {
synchronized (MyLock.lockb) {
System.out.println("else lockb");
synchronized (MyLock.locka) {
System.out.println("else locka");
}
}
}
}
}
}
class MyLock {
static Object locka = new Object();
static Object lockb = new Object();
}
我的输出结果:
else lockb
if locka//到这里就锁住了。
线程间通讯
等待唤醒机制
主要方法:wait();notify();notifyAll();
都使用在同步中,因为要对持有监视器(锁)的线程操作,所以必须在同步中使用
这些方法在操作同步中线程时,都必须要标识他们所操作线程中的锁对象。只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。不可以对不同锁中的线程进行唤醒。也就是说等待和唤醒必须是同一个锁。
示例:
/*
需求:多个线程操作同一个资源。
如,一个线程存名字和性别。另外一个线程获取姓名和性别。
*/
public class InputOutput {
public static void main(String[] args) {
Person p = new Person();
Input i = new Input(p);
Output o = new Output(p);
Thread t = new Thread(i);
Thread t1 = new Thread(o);
t.start();
t1.start();
}
}
class Input implements Runnable {
private Person p;
public Input(Person p) {
this.p = p;
}
@Override
public void run() {
while (true) {
System.out.println(p.name + " ... " + p.sex);
}
}
}
class Output implements Runnable {
private Person p;
public Output(Person p) {
this.p = p;
}
@Override
public void run() {
boolean flag = true;
while (true) {
if (flag) {
p.name = "张三";
p.sex = "男";
flag = false;
} else {
p.name = "divid";
p.sex = "women";
flag = true;
}
}
}
}
class Person {
String name;//此为演示代码,工作中一般将属性私有化,并提供get和set方法
String sex;
}
得到结果为:
//部分结果
张三 ... 男
divid ... women
divid ... 男
张三 ... 男
divid ... women
张三 ... 男
张三 ... women
divid ... women
张三 ... women
可以看出,多线程之间通讯存在安全隐患。修改代码(加锁):
public class InputOutput {
public static void main(String[] args) {
Person p = new Person();
Input i = new Input(p);
Output o = new Output(p);
Thread t = new Thread(i);
Thread t1 = new Thread(o);
t.start();
t1.start();
}
}
class Input implements Runnable {
private Person p;
public Input(Person p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized(p) {//此处的锁是用的p(因为p是两个线程操纵的共同数据,记住:为保证同步,锁的对象必须相同)
System.out.println(p.name + " ... " + p.sex);
}
}
}
}
class Output implements Runnable {
private Person p;
public Output(Person p) {
this.p = p;
}
@Override
public void run() {
boolean flag = true;
while (true) {
synchronized(p) {
if (flag) {
p.name = "张三";
p.sex = "男";
flag = false;
} else {
p.name = "divid";
p.sex = "women";
flag = true;
}
}
}
}
}
class Person {
String name;//此为演示代码,工作中一般将属性私有化,并提供get和set方法
String sex;
}
结果:
张三 ... 男
张三 ... 男
张三 ... 男
张三 ... 男
张三 ... 男
张三 ... 男
张三 ... 男
张三 ... 男
张三 ... 男
divid ... women
divid ... women
divid ... women
divid ... women
divid ... women
divid ... women
divid ... women
得到的结果虽然真确了,但是却不是我要的:存一个,打印一个。修改后
//优化后的代码
public class InputOutput {
public static void main(String[] args) {
Person p = new Person();
new Thread(new Input(p)).start();//生产1
new Thread(new Input(p)).start();//生产2
new Thread(new Output(p)).start();//消费1
new Thread(new Output(p)).start();//消费2
}
}
class Input implements Runnable {
private Person p;
public Input(Person p) {
this.p = p;
}
@Override
public void run() {
while (true) {
p.out();
}
}
}
class Output implements Runnable {
private Person p;
public Output(Person p) {
this.p = p;
}
@Override
public void run() {
int x = 0;
while (true) {
if (x == 0) {
p.setPerson("张三","男");
} else {
p.setPerson("Divid","women");
}
x = (x + 1) % 2;
}
}
}
class Person {
private String name;
private String sex;
boolean flag = false;
//此为演示代码,一般将属性私有化,并提供get和set方法
//使用this作为锁的对象
public synchronized void setPerson(String name,String sex) {
while(this.flag) {//加入两个村的线程都wait了。notifyAll唤醒所有线程后就会再次判断标记,如果是if则直接往下运行,会出现生产两次,消费一次、生产一次消费两次的情况
try {this.wait();} catch (InterruptedException e) {}
}
this.name = name;
this.sex = sex;
this.flag = true;
//唤醒所有线程(flag判断有if变成while后)(因为线程是先进先出,假设生产2生产一次后wait了,然后生产1判断标记后wait了,再消费1消费一次后wait了,接着唤醒一次,消费2wait了。生产2就活了,2生产一次,改变标记,唤醒一次,生产1醒了,判断标记后又wait,所有的线程都wait了。。。就挂了。所以必须notifyAll)
this.notifyAll();
}
public synchronized void out() {
while(!this.flag) {
try {this.wait();} catch (InterruptedException e) {}
}
System.out.println(this.name + " ... ... " + this.sex);
this.flag = false;
this.notifyAll();
}
}
运行结果正常。
//可以copy代码自己运行下
张三 ... 男
divid ... women
张三 ... 男
divid ... women
张三 ... 男
divid ... women
解释为什么要用notifyAll而不是notify
JDK1.5之后用lock(多个Condition对象,可以完美的控制锁)
import java.util.concurrent.locks.*;
public class ProduceTest {
public static void main(String[] args) {
Resource r = new Resource();
Consumer c = new Consumer(r);
Producer p = new Producer(r);
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
Thread t3 = new Thread(p);
Thread t4 = new Thread(p);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Consumer implements Runnable {
private Resource r;
public Consumer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.out();
}
}
}
class Producer implements Runnable {
private Resource r;
public Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.setResource("zhangsan");
}
}
}
class Resource {
private String name;
private int count = 0;
boolean flag = false;
private final Lock lock = new ReentrantLock();
private final Condition p_cd = lock.newCondition();//多个Condition对象
private final Condition c_cd = lock.newCondition();//可以查看api:java.util.concurrent.locks 包里面
public void setResource(String name) {
lock.lock();
try {
while (this.flag) {
//生产的锁
try {p_cd.await();} catch (InterruptedException e) {}
}
this.name = name + "--" + count++;
System.out.println(Thread.currentThread().getName()+ " ....生产者.... " + this.name );
this.flag = true;
//唤醒消费线程
c_cd.signal();
} finally {//解锁(必须做)
lock.unlock();
}
}
public void out() {
lock.lock();
try {
while (!this.flag) {
//消费的锁
try {c_cd.await();} catch (InterruptedException e) {}
}
System.out.println(Thread.currentThread().getName()+ " ............消费者........... " + this.name );
this.flag = false;
//唤醒生产的线程
p_cd.signal();
} finally {//解锁(必须做)
lock.unlock();
}
}
}
创建线程的三种方式
- 继承Thread类
- 实现Runnable接口
- 使用ExecutorService、Callable、Future实现有返回结果的线程
Callable和FutureTask实现多线程
public class CallableTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<String> c = new CallableDemo();
FutureTask<String> ft = new FutureTask<String>(c);
FutureTask<String> ft1 = new FutureTask<String>(c);
Thread t1 = new Thread(ft);
Thread t2 = new Thread(ft1);
t1.start();
t2.start();
}
}
class CallableDemo implements Callable<String> {
private int count = 1;
@Override
public String call() throws Exception {
System.out.println("CallableDemo count++ = " + (count++));
return null;
}
}
ExecutorService、Callable、Future线程池实现(有返回值的线程)
参考:FelixZh
public class CallableTest {//包都是java.util.concurrent下的
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(5);
List<Future<String>> list = new ArrayList<Future<String>>();
for (int x=0; x<5; x++) {
Future<String> submit = es.submit(new CallableDemo());
list.add(submit);
}
es.shutdown();
for (Future<String> f : list) {
System.out.println(f.get().toString());
}
}
}
class CallableDemo implements Callable<String> {
private int count = 1;
@Override
public String call() throws Exception {
System.out.println("CallableDemo count++ = " + (count++));
return "CallableDemo count++ = " + (count++);
}
}
还有守护线程,停止线程,线程优先级,yield和join方法等等。