1. 理解程序、进程、线程的概念
程序:静态的代码
进程:执行中的程序
线程:可以理解为进程的多条执行线索,每条线索又对应着各自独立的生命周期
2. 如何创建java程序的线程(重点)
1) 创建多线程的方式一:继承方式实现
package cn.km.thread01;
class SubThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public SubThread1(String name) {
super(name);
}
}
class SubThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public SubThread2(String name) {
super(name);
}
}
public class TestThread01 {
public static void main(String[] args) {
SubThread1 st1 = new SubThread1("线程1");
SubThread2 st2 = new SubThread2("线程2");
st1.setPriority(Thread.MIN_PRIORITY);//设置优先级最低 1
st2.setPriority(Thread.MAX_PRIORITY); //设置优先级最高 10
st1.start();
st2.start();
}
}
2) 多线程实现方式二:实现方式实现
创建一个实现了Runnable接口的类
实现接口的抽象方法
创建一个Runnable接口实现类的对象
将此对象作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程
启动线程,执行Thread的start方法。
package cn.km.thread01; class subThread implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i%2 != 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class TestThread02 { public static void main(String[] args) { subThread target = new subThread(); Thread thread1 = new Thread(target); Thread thread2 = new Thread(target); Thread thread3 = new Thread(target); thread1.setName("线程1"); thread2.setName("线程2"); thread3.setName("线程3"); thread1.start(); thread2.start(); thread3.start(); } }
3) 两者的异同
联系:Thread也实现了Runnable,
哪个方式好??实现方式优于继承方式
Why? 避免了java单继承的局限性
如果多个线程要操作同一份资源(或数据),更适合使用实现的方式
4) 模拟窗口售票代码(此代码有隐患)
package cn.km.thread01; class Window implements Runnable{ int ticket = 100; @Override public void run() { while(true){ if(ticket > 0){ // try { // Thread.currentThread().sleep(10); // } catch (InterruptedException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket--); }else{ break; } } } } public class TestThread03 { public static void main(String[] args) { Window target = new Window(); Thread thread1 = new Thread(target); Thread thread2 = new Thread(target); Thread thread3 = new Thread(target); thread1.setName("窗口1"); thread2.setName("窗口2"); thread3.setName("窗口3"); thread1.start(); thread2.start(); thread3.start(); } }
3. 线程的常用方法
1) start(),启动线程并执行相应的run()方法
2) run():子线程要执行的代码放入run()方法中
3) currentThread() :静态的,调取当前的线程
4) getName() :获取此线程的名称
5) setName() :设置此线程的名称
6) yield() :调用此方法的线程释放当前CPU的执行权
7) join():在A线程中调用B线程的join()方法,表示当执行到此方法,A线程停止执行,直至B线程执行完毕,A线程再接着join()之后的代码执行
8) isAlive() :判断当前线程是否还存活
9) sleep(long l) :显示的让当前线程睡眠1毫秒
10) 线程的通信:wait() notify() notifyAll()
11) 设置线程的优先级
getPriority() :返回线程优先值
setPriority(intnewPriority) :改变线程的优先级【MAX_PRIORITY=10;MIN_PRIORITY =1;NORM_PRIORITY=5;】
4. 线程的生命周期
5. 线程的同步机制(重点、难点)
package cn.km.thread02;
/**
* 此程序存在线程安全问题:打印车票时,会出现重票、错票
* 1.线程安全问题存在的原因
* 由于一个线程再操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题
* 2.如何解决线程安全问题
* 必须让一个线程操作共享数据完毕后,其他线程才有机会参与共享数据的操作
* 3.java如何实现线程的安全:线程同步机制
* 方式一:同步代码块
* synchronized(同步监视器){
* //需要被同步的代码块(即为操作共享数据的代码)
* }
* 共享数据:多个线程共同操作的同一个数据(变量)
* 同步监视器:由一个类的对象充当。哪个线程获取此监视器,谁就执行大括号中被同步的代码。俗称:锁
* 要求:所有线程必须公用一把锁
* 注意:在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this
* 方式二:同步方法
*/
@SuppressWarnings("static-access")
class Window implements Runnable{
int ticket = 100;
@Override
public void run() {
while(true){
synchronized (this) { //加锁
if (ticket > 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket--);
} else {
break;
}
}
}
}
}
public class TestWindow01 {
public static void main(String[] args) {
Window target = new Window();
Thread thread1 = new Thread(target);
Thread thread2 = new Thread(target);
Thread thread3 = new Thread(target);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread1.start();
thread2.start();
}
}
此程序存在线程安全问题:打印车票时,会出现重票、错票
1.线程安全问题存在的原因
由于一个线程再操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题
2.如何解决线程安全问题
必须让一个线程操作共享数据完毕后,其他线程才有机会参与共享数据的操作
3.java如何实现线程的安全:线程同步机制
6. 线程的同步实现(重点、难点)
1) 同步代码块
synchronized(同步监视器){
//需要被同步的代码块(即为操作共享数据的代码)
}
共享数据:多个线程共同操作的同一个数据(变量)
同步监视器:由一个类的对象充当。哪个线程获取此监视器,谁就执行大括号中被同步的代码。俗称:锁
要求:所有线程必须公用一把锁
注意:在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this
2) 同步方法
package cn.km.thread02; /** * 此程序存在线程安全问题:打印车票时,会出现重票、错票 * 1.线程安全问题存在的原因 * 由于一个线程再操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题 * 2.如何解决线程安全问题 * 必须让一个线程操作共享数据完毕后,其他线程才有机会参与共享数据的操作 * 3.java如何实现线程的安全:线程同步机制 * 方式一:同步代码块 * synchronized(同步监视器){ * //需要被同步的代码块(即为操作共享数据的代码) * } * 共享数据:多个线程共同操作的同一个数据(变量) * 同步监视器:由一个类的对象充当。哪个线程获取此监视器,谁就执行大括号中被同步的代码。俗称:锁 * 要求:所有线程必须公用一把锁 * 注意:在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this * 方式二:同步方法 */ @SuppressWarnings("static-access") class Window3 implements Runnable{ int ticket = 100; @Override public void run() { while(true){ sell(); } } public synchronized void sell(){ //添加同步方法 if (ticket > 0) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket--); } } } public class TestWindow02 { public static void main(String[] args) { Window3 target = new Window3(); Thread thread1 = new Thread(target); Thread thread2 = new Thread(target); thread1.setName("窗口1"); thread2.setName("窗口2"); thread1.start(); thread2.start(); } }
7. 线程的通信
Notify/notifyAll
Wait
注意:java.lang.Object提供的这三个方法只有在synchronized方法或代码块中才能使用,否则会有异常:java.lang.IllegalMonitorStateException
package cn.km.thread02; /** * */ @SuppressWarnings("static-access") class Window4 implements Runnable{ int ticket = 100; @Override public void run() { while(true){ sell(); } } public synchronized void sell(){ //添加同步方法 notify(); //唤醒线程 if (ticket > 0) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket--); } //线程等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public class TestWindow03 { public static void main(String[] args) { Window4 target = new Window4(); Thread thread1 = new Thread(target); Thread thread2 = new Thread(target); thread1.setName("窗口1"); thread2.setName("窗口2"); thread1.start(); thread2.start(); } }
8. 生产者和消费者问题
生产者(Productor)将产品交给店员(Clerk),消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品,如果生产者视图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
package cn.km.thread03; /** * 生产者/消费者问题 生产者(Productor)将产品交给店员(Clerk), 消费者(Customer)从店员处取走产品, * 店员一次只能持有固定数量的产品,如果生产者视图生产更多的产品,店员会叫生产者停一下, 如果店中有空位放产品了再通知生产者继续生产; * 如果店中没有产品了,店员会告诉消费者等一下, 如果店中有产品了再通知消费者来取走产品。 * * * 分析: 1.是否涉及到多线程的问题? 是 生产者/消费者 2.是否涉及共享数据 ? 有 需要考虑线程安全 3.此共享数据为谁? 产品的数量 4.是否涉及到线程的通信呢? 存在生产者、消费者的通信; */ class Clerk { // 店员 int product; public synchronized void addProduct() {// 生产产品 if (product >= 20) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { product++; System.out.println(Thread.currentThread().getName() + ":生产了第" + product + "个产品"); notifyAll(); } } public synchronized void consumeProduct() { // 消费产品 if (product <= 0) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println(Thread.currentThread().getName() + ":消费了第" + product + "个产品"); product--; notifyAll(); } } } class Producer implements Runnable { Clerk clerk; public Producer(Clerk clerk) { this.clerk = clerk; } public void run() { System.out.println("生产者开始生产产品"); while (true) { try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } clerk.addProduct(); } }; } class Consumer implements Runnable { Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } public void run() { System.out.println("消费者开始消费产品"); while (true) { try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } clerk.consumeProduct(); } }; } public class ProductorCustomer { public static void main(String[] args) { Clerk clerk = new Clerk(); Producer producer1 = new Producer(clerk); Consumer consumer1 = new Consumer(clerk); Thread t1 = new Thread(producer1); Thread t2 = new Thread(consumer1); Thread t3 = new Thread(producer1); t1.setName("生产者1"); t2.setName("消费者"); t3.setName("生产者2"); t1.start(); t2.start(); t3.start(); } }
9. 面试题总结
1) wait 和 sleep 方法的区别
1)这两个方法来自不同的类分别是 sleep来自Thread类,和wait来自Object
sleep是Thread的静态方法,谁调用谁就去睡觉,
即使在a线程里调用b线程的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep????????
2)锁:最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他的线程可以使用同步控制块或方法
sleep不出让系统资源
wait是进入线程等待池等待,出让系统资源,其他线程可以占用cpu。
wait不加时间限制,要等待其他的notify/notifyAll唤醒等待池中所有的线程,才会进入就绪队列等待分配系统资源。。
sleep(milliseconds)可以使用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
3)使用范围:wait、notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
synchronized(){
notify();//notifyAll();
//wait();
}
1) 创建线程的3种方式
2) 什么是线程安全继承Thread
实现Runnable接口
实现Callable接口
package cn.km.thread05; import java.util.concurrent.Callable; public class MyCallable implements Callable{ @Override public Object call() throws Exception { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ":"+i); } return null; } }
public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(2); pool.submit(new MyCallable()); pool.submit(new MyCallable()); }
3) Runnable和Callable接口的区别
4) synchronized、lock、ReentrantLock、ReadWriteLock
5) 介绍下CAS(无锁技术)
6) 什么是ThreadLocal
7) 创建线程池的4中方式
8) ThreadPoolExecutor的内部工作原理
9) 分布式环境下,怎么保持线程安全
正在整理中。。。