1 程序、线程、进程基本概念
程序:为完成特定任务,用某种语言编写的一组指令的集合。(一段静态代码)
进程:程序的一次执行过程,伙食正在运行的一个程序。进程是资源分配的单位。
线程:进程可进一步细化为线程,是一个程序内部的一条之星路径。线程是调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC)。
多线程:若一个进程同一时间并行执行多个线程,就是支持多线程的。
一个java.exe程序至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事情。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀,多个人做同一件事。
2 线程的创建和使用
1.线程的创建
1.继承于Thread类
1.非匿名类
1、创建一个继承于Thread类的子类
2、重写Thread类的run()
3、创建Thread类的子类对象
4、通过此对象调用start()
问题一:不能直接调用run()方法启动线程,这样会把run()方法当做一个普通的方法
问题二:不能再一次调用start()方法再启动一个线程,需要再new一个MyThread
// 1、创建一个继承于Thread类的子类
class MyThread extends Thread{
@Override
// 2、重写Thread类的run()
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
public class MyTest {
@Test
public void test(){
// 3、创建Thread类的子类对象
MyThread thread = new MyThread();
// 4、通过此对象调用start()
// 启动当前线程,调用当前线程的run()
thread.start();
// 以下操作依然是在main()中执行
for (int i = 0; i < 100; i++) {
System.out.println("****");
}
}
}
2.匿名类
public class MyTest {
@Test
public void test(){
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(i%2!=0){
System.out.println(i);
}
}
}
}.run();
}
}
2.实现Runnable接口
// 1、实现Runnable接口
class MyThread1 implements Runnable{
// 2、实现run()方法
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
public class aaa {
@Test
public void test1(){
// 3、创建实现类的对象
MyThread1 thread = new MyThread1();
// 4、把对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t = new Thread(thread);
// 5、调用start()方法
t.start();
}
}
买票程序
package com.ll;
import org.junit.Test;
class Window1 implements Runnable{
private int ticket=100;
public void run() {
while (true){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+" 票号:"+ticket);
ticket--;
}else {
break;
}
}
}
}
public class MyTest2 {
@Test
public void test(){
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
3.继承于Thread类与实现Runnable接口的比较
开发中优先选择实现Runnable接口方式
1、实现的方式没有类的单继承的局限性
2、更适合处理多个线程有共享数据的情况
联系:
Thread本身就实现了Runnable接口。class Thread implements Runnable
2.线程的使用
1.常用方法
start():启动当前线程,调用当前线程的run()
run():通常要重写thread类中的此方法,将创建的线程和要执行的操作声明在此方法中
Thread.currentThread():静态方法,返回当前代码执行的线程
Thread.currentThread().getName():获取当前线程的名字
Thread.currentThread().setName(“mythread”);:在start()之前,设置当前线程的名称
yield():释放当前CPU的执行权,有可能再下一次又获取到了执行权
join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,知道线程b完全执行完后,线程a才结束阻塞状态。
sleep(1000):只能try-catch捕获异常,子类不能抛出比父类更大的异常。让当前线程睡眠指定的时间(毫秒),在指定的时间内,当前线程是阻塞状态。
isAlive():判断线程是否存活。
2.线程的调度
1.调度策略
- 时间片:线程切换
- 抢占式:高优先级的线程抢占CPU
2.调度方法
- 对优先级线程组成的先进先出队列(先到先服务),使用时间片策略
- 对高优先级,使用有线调度的抢占式策略
3.线程优先级
优先级越高,越可能先执行。
thread.getPriority();// 获取线程优先级
thread.setPriority();// 设置线程优先级
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5; // 默认优先级
public final static int MAX_PRIORITY = 10;
3 线程的生命周期
- 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
4 线程的同步(解决线程安全问题)
线程的安全问题:
多个线程执行的不确定性会引起执行结果的不稳定。
多个线程对账本的共享,会造成操作的不完整性,会破坏数据。
同步的方式,解决了线程安全问题。
操作同步代码时,只能有一个线程参与,其他线程等待。相当于一个单线程的过程,效率低。
synchronized和Lock的区别
synchronized机制在执行完相应的同步代码之后,自动释放同步监视器
Lock需要手动的启动同步(Lock()),同时结束同步也需要手动的实现(unlock())
优先使用顺序
Lock→同步代码块(已经进入方法体,分配了相应的资源)→同步方法(在方法体之外)
1.同步代码块
synchronized (object同步监视器){}
1.实现Runnable接口
package com.ll;
import org.junit.Test;
class Window1 implements Runnable{
// 2、共享数据:多个线程共同操作的变量
private int ticket=100;
Object object=new Object();
public void run() {
while (true){
// 1、操作共享数据的代码,即为需要被同步的代码
// 3、synchronized (同步监视器) 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。要求多个线程必须要公用同一把锁
synchronized (object){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+" 票号:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class MyTest2 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
2.继承于Thread类
同步监视器可以是当前类。
2.同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们可以将此方法声明为同步的。
1、同步方法仍然涉及到同步监视器,只是不需要我们显式声明。
2、非静态同步方法,同步监视器是this
3、静态同步方法,同步监视器是当前类本身。
1.实现Runnable接口
class Window2 implements Runnable{
// 2、共享数据:多个线程共同操作的变量
private int ticket=100;
Object object=new Object();
public void run() {
while (true){
show();
}
}
// 同步监视器是this
private synchronized void show(){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+" 票号:"+ticket);
ticket--;
}
}
}
2.继承于Thread类
package com.ll;
class Window2 extends Thread{
// 2、共享数据:多个线程共同操作的变量
private static int ticket=100;
Object object=new Object();
public void run() {
while (true){
show();
}
}
// 同步监视器是当前类
private static synchronized void show(){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+" 票号:"+ticket);
ticket--;
}
}
}
public class MyTest3 {
public static void main(String[] args) {
Window2 w1 = new Window2();
Window2 w2 = new Window2();
Window2 w3 = new Window2();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
3.Lock锁方式解决线程安全
4.线程安全的单例模式之懒汉式
1.synchronized同步方法
class Bank{
private Bank(){}
private static Bank instance=null;
public static synchronized Bank getInstance(){
if (instance==null){
instance=new Bank();
}
return instance;
}
}
2.synchronized同步代码块
方式一:效率低
class Bank{
private Bank(){}
private static Bank instance=null;
public static Bank getInstance(){
synchronized (Bank.class){
if (instance==null){
instance=new Bank();
}
}
return instance;
}
}
方式二:效率低高
class Bank{
private Bank(){}
private static Bank instance=null;
public static Bank getInstance(){
if(instance==null){
synchronized (Bank.class){
if (instance==null){
instance=new Bank();
}
}
}
return instance;
}
}
5 线程的通信
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify():会唤醒被wait()的一个线程,如果有多个线程被wait,则唤醒优先级高的那个
notify()All():唤醒所有被wait()的线程
注意:
1、只能出现在同步代码块或者同步方法中,不能使用Lock()。
2、wait()、notify()、notify()All()三个方法调用者必须是同步代码块或者同步方法的同步监视器。否则会报IllegalMonitorStateException异常。
3、wait()、notify()、notify()All()三个类是定义在java.lang.Object类中。
1.两个线程交替打印数字
class Number implements Runnable{
private int number=1;
public void run() {
while (true){
synchronized (this){
notify();
if(number<100){
System.out.println(Thread.currentThread().getName()+": "+number);
number++;
try {
// 是的调用wait()方法的线程进入阻塞状态
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
2.生产者消费者
class Clerk{
private int product=0;
public synchronized void produceProduct() {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(product<20){
product++;
System.out.println(Thread.currentThread().getName()+" 生产了一件,目前 "+product);
// 生产了一件产品,唤醒消费者
notify();
}else {
// 等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumeProduct() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(product>0){
product--;
System.out.println(Thread.currentThread().getName()+" 消费了一件,目前 "+product);
// 消费了一件产品,唤醒生产者
notify();
}else {
// 等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Productor extends Thread{
private Clerk clerk;
public Productor(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run() {
System.out.println("开始生产产品");
while (true){
clerk.produceProduct();
}
}
}
class Cutomer extends Thread{
private Clerk clerk;
public Cutomer(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run() {
System.out.println("开始消费产品");
while (true){
clerk.consumeProduct();
}
}
}
public class Sall {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Cutomer cutomer = new Cutomer(clerk);
productor.setName("生产者");
cutomer.setName("消费者");
productor.start();
cutomer.start();
}
}
6 jdk5新增的线程创建方式
1.实现Callable接口
实现Callable接口比实现Runnable接口强大:
- Callable相比run()方法,有返回值
- 可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类(Future接口的唯一实现类),比如获取返回结果
class Call implements Callable {
public Object call() throws Exception {
int sum=0;
for (int i = 0; i <= 100; i++) {
if(i%2==0){
System.out.println(i);
sum+=i;
}
}
return sum;
}
}
public class CallableThread {
public static void main(String[] args) {
Call call = new Call();
FutureTask futureTask=new FutureTask(call);
Thread thread = new Thread(futureTask);
try {
thread.start();
Object o = futureTask.get();
System.out.println("100以内偶数和"+o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
2.线程池
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。
好处
- 提高响应速度(减少创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次创建)
- 便于线程管理
--------corePoolSize核心池的大小
--------maximumPoolSize最大线程数
--------keepAliveTime线程没有任务时最多保持多长时间后会终止
Executors是线程池的工具类
class NumberThread implements Runnable {
public void run() {
for (int i = 0; i <= 100; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}
public class PoolThread {
public static void main(String[] args) {
// 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10); // ExecutorService接口
// 设置线程池的属性
ThreadPoolExecutor service1= (ThreadPoolExecutor) service;
// service1.setCorePoolSize();
// service1.setMaximumPoolSize();
// service1.setKeepAliveTime();
service1.execute(new NumberThread()); // 适合Runnable
// service.submit(Callable<T> task); // 适合Callable
// 关闭连接池
service1.shutdown();
}
}