一、相关概念
注意:
1、不同进程之间不共享内存
2、进程之间的数据交换和通信成本很高
线程调度:
单核CPU与多核CPU:
并行与并发:
二、创建和启动线程
1、概述
2、方式
2.1 方式一:继承Thread类
2.2 方式二:实现Runnable接口
class CountNumber implements Runnable{
for(int i=0;i<10;i++){
print(i);
}
}
class Test{
public static void main(String args[]){
CountNumber c = new CountNumber();
Thread t = new Thread(c);
t.start();
}
}
2.3 方式三:Callable(JDK5.0后新增)
2.4 线程池
代码示例:
三、Thread类常用结构
1、构造器
2、常用方法
四、多线程的生命周期
JDK1.5之前:
JDK1.5之后:
五、线程安全问题
引入
public class Test3 {
public static void main(String[] args) {
saleTikect s =new saleTikect();
Thread t1=new Thread(s);
Thread t2=new Thread(s);
Thread t3=new Thread(s);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//实现Runnable接口
class saleTikect implements Runnable{
int tikect=100;
@Override
public void run(){
while (true) {
if (tikect>0) {
System.out.println(Thread.currentThread().getName()+"售票,票号:"+tikect);
tikect--;
}else{
break;
}
}
}
}
出现问题:
解决方式:线程的同步机制
方式1:同步代码块
格式:
synchornized(同步监视器){
//需要被同步的代码
}
代码:
public class Test3RunnableSafe {
public static void main(String[] args) {
saleTikect s =new saleTikect();
Thread t1=new Thread(s);
Thread t2=new Thread(s);
Thread t3=new Thread(s);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class saleTikect implements Runnable{
static int tikect=100;
Object obj=new Object();
@Override
public void run(){
while (true) {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
synchronized(obj){
//obj唯一
if (tikect>0) {
try {
Thread.sleep(5);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票号:"+tikect);
tikect--;
}else{
break;
}
}
}
}
}
说明:
>需要被同步的代码,即为操作共享数据的代码
>共享数据,即为多个线程需要操作的数据
>需要被同步的代码,在被synchornized包裹以后,
就使得一个线程在操作这些代码的过程中,其他进必须等待
>同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步到代码
>同步监视器,可以使用任何一个类的对象充当。
>多个线程必须共用同一个同步监视器
注意:在实现Runnable接口方式中,同步监视器可以考虑用this
在jichengThread类方式中,同步监视器慎用this,可以可以考虑使用:当前类.class
方式2:同步方法
说明:
>如果操作共享数据的代码完整的声明在一个方法中,将此方法声明为同步方法即可。
>非静态同步方法,默认同步监视器是this;静态同步方法,默认同步监视器是当前类本身。
代码:
public class Test3 {
public static void main(String[] args) {
saleTikect s = new saleTikect();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class saleTikect implements Runnable {
int tikect = 100;
boolean isFlag=true;
@Override
public void run() {
while (isFlag) {
show();
}
}
/**
* 关键代码完整声明在show()中
*/
public synchronized void show() {
if (tikect > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号:" + tikect);
tikect--;
}else{
isFlag=false;
}
}
}
正确:
解决继承类出现的问题。
问题代码:
public class Test3two {
public static void main(String[] args) {
Window window1 = new Window();
Window window2 = new Window();
Window window3 = new Window();
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
class Window extends Thread{
static int tikect=100;
@Override
public void run(){
while (true) {
if (tikect>0) {
System.out.println(Thread.currentThread().getName()+"售票,票号:"+tikect);
tikect--;
}else{
break;
}
}
}
}
出现重票:
解决代码:
public class Test3two {
public static void main(String[] args) {
Window window1 = new Window();
Window window2 = new Window();
Window window3 = new Window();
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
class Window extends Thread{
static int tikect=100;
static boolean isFlag=true;
@Override
public void run(){
while (isFlag) {
show();
}
}
/*public synchronized void show(){//此时同步监视器this:window1,window2,window3。
*/
public static synchronized void show(){
//isFlag也要加上static
//此时同步监视器:当前类,即为window1.class,这是唯一的
if (tikect>0) {
System.out.println(Thread.currentThread().getName()+"售票,票号:"+tikect);
tikect--;
}else{
isFlag=false;
}
}
}
运行结果:
六、懒汉式的线程安全问题、死锁、Lock的使用
1、懒汉式线程安全问题
案例代码:
public class Test4 {
static Bank b1 = null;
static Bank b2 = null;
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run(){
b1=Bank.getInstance();
}
};
Thread t2 = new Thread(){
@Override
public void run(){
b2=Bank.getInstance();
}
};
t1.start();
t2.start();
try {
t1.join();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
try {
t1.join();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(b1);
System.out.println(b2);
System.out.println(b1==b2);
}
}
class Bank{
private Bank(){}
private static Bank instance=null;
public static Bank getInstance(){
if(instance==null){
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
instance=new Bank();
}
return instance;
}
}
运行结果:
理想结果两个地址应该是一样的,但是出现了线程安全问题。
方式一:
public class Test4 {
static Bank b1 = null;
static Bank b2 = null;
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run(){
b1=Bank.getInstance();
}
};
Thread t2 = new Thread(){
@Override
public void run(){
b2=Bank.getInstance();
}
};
t1.start();
t2.start();
try {
t1.join();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
try {
t1.join();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(b1);
System.out.println(b2);
System.out.println(b1==b2);
}
}
class Bank{
private Bank(){}
private static Bank instance=null;
//方式一:同步监视器
public synchronized static Bank getInstance(){
//同步监视器为当前类,是唯一的
if(instance==null){
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
instance=new Bank();
}
return instance;
}
}
运行结果:
方式二:
//方式二:同步监视器(同步代码块)
public static Bank getInstance(){
synchronized(Bank.class){
if(instance==null){
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
instance=new Bank();
}
}
return instance;
}
方式三:
//方式三:多套一层判断
//当两个线程同时进入第一层判断instance为null时,
//某一线程进入关键代码创建instance(synchronized只允许一个线程执行
//另一线程进入等待,当某一线程完成创建,释放同步监视器
//另一线程在第二层判断时instance!=null,return instance
//此时两个线程得到的instance是相同的
//若后续线程进入,在第一层判断时instance不为null,直接返回相同地址
//方式三相较于前两种效率更高,但是存在指令重排问题
//(还没有初始化完成就拿着对象出去了,创建对象的准备工作是很多的,虽然这里代码只有一行)
//可加关键字volatite,将instance声明为volatite
private static volatile Bank instance=null;
public static Bank getInstance(){
if(instance==null){
synchronized(Bank.class){
if(instance==null){
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
instance=new Bank();
}
}
}
return instance;
}
2、死锁问题
举例:
public class DeadLock {
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run(){
synchronized(s1){
s1.append("A");
s2.append("1");
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}
synchronized(s2){
s1.append("B");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(){
@Override
public void run(){
synchronized(s2){
s1.append("C");
s2.append("3");
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}
synchronized(s1){
s1.append("D");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
}
}
出现死锁:
3、Lock
针对购票问题:
代码:
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
Window window1 = new Window();
Window window2 = new Window();
Window window3 = new Window();
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
class Window extends Thread{
static int tikect=100;
//创建Lock的实例,确保多个线程共用同一个Lock实例,需要考虑将Lock声明为static final
private static final ReentrantLock lock = new ReentrantLock();
@Override
public void run(){
while (true) {
//2.执行lock()方法,锁定对共享资源的调用
lock.lock();
try{
if (tikect>0) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName()+"售票,票号:"+tikect);
tikect--;
}else{
break;
}
}finally{
//3.释放对同步共享数据的锁定
lock.unlock();
}
}
}
}
运行结果:
七、线程的通信(生产者消费者问题)
等待唤醒机制:
注意点:
案例:生产者&消费者
/*
* 1、多线程:生产者、消费者
* 2、共享数据:产品
* 3、需要处理线程安全问题:使用同步机制
* 4、存在线程通信
*/
public class PCTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
producer.setName("生产者1");
consumer.setName("消费者1");
producer.start();
consumer.start();
}
}
class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run(){
while (true) {
System.out.println("生产产品中......");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
// TODO: handle exception
}
clerk.addProduct();
}
}
}
class Consumer extends Thread{
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run(){
while (true) {
System.out.println("消费产品......");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
// TODO: handle exception
}
clerk.minusProduct();
}
}
}
class Clerk{
private int productNumber=0;
public synchronized void addProduct(){
if(productNumber>=20){
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
// TODO: handle exception
}
}
productNumber++;
System.out.println(Thread.currentThread().getName()+"生产了第"+productNumber+"个产品");
}
public synchronized void minusProduct(){
if(productNumber<=0){
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
// TODO: handle exception
}
}
System.out.println(Thread.currentThread().getName()+"消费了第"+productNumber+"个产品");
productNumber--;
}
}
wait()和sleep()的区别: