线程开启不一定立即执行,由CPU调度执行
三种创建方式
1.继承Thread类(重点)
1.声明一个类继承为Thread的子类2.子类重写Thread类中的run方法3.创建线程对象,调用start()方法启动线程
Public class thread extends Thread{@Overridepublic void run(){//run方法线程体}public static void main(String[] args){Thread thread = new Thread();thread.start();//可简化为 new Thread().start();}}
2.实现Runnable接口(重点 推荐使用,方便灵活,方便同一个对象被多个线程使用)
1.定义MyRunnable类实现Runnable接口2.实现run()方法,编写线程执行体3.创建线程对象,调用start()方法启动线程
Public class MyRunnable implements Runnable{@Overridepublic void run(){//run方法线程体}public static void main(String[] args){//创建Runnable接口的实现类对象MyRunnable myrunnable = new MyRunnable();//创建线程对象,通过线程对象来开启我们的线程(代理)Thread thread = new Thread(myrunnable);thread.start();//可简化为 new Thread(myrunnable).start();}}
3.实现Callable接口(了解)
Lamda表达式:
lamda表达式只能应用于函数式接口(接口中只能有一个抽象方法)
//定义一个接口public interface ILove{void Love(int a);}//lamda表达式public static void main(String[] args){ILove love = null;//1.lamda表达式简化love = (int a)->{System.out.println("I Love You");};//2.简化参数类型(如果有一个参数的话可以去掉括号)love = (a)->{System.out.println("I Love You");};//3.简化为一行love = (a)->System.out.println("I Love You");}总结:1.Lamda表达式只能有一行代码的情况下才可以简化为一行,如果存在多行,那么只能有代码块包裹2.使用lamda表达式的前提是必须是函数式接口3.多个参数也可以去掉参数类型,要去掉就都去掉,但必须加上括号
2、线程的状态 setPriority(int newPriority):更改线程的优先级
static void sleep(long millis):指定线程休眠
void join():插队线程
static void yield():暂停当前正在执行的线程对象,并执行其他线程
boolean isAlive():测试线程是否处于活动状态
停止线程
不推荐使用JDK提供的stop(),destroy()方法。(已经废弃)
推荐线程自己停止下来,建议使用一个标志位进行终止变量,当flag=false,则终止线程运行。
class TestStop implements Runnable{private boolean flag = true;@Verridepublic void run(){int i=0;while(flag){//输出i的值,并自加}}//线程停止方法void stop(){this.flag = flag;}public static void main(String[] args){//开启线程,去执行run方法TestStop teststop = new TestStop();new Thread(teststop).start();//main()线程for(int i=0;i<999;i++){if(i==900){//设置标志位,停止线程teststop.stop();}}}}
线程礼让(yield)
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转化为就绪状态
让CPU重新调度,礼让不一定成功
public class TestYield{public static void main(String[] args){MyYield myYield = new MyYield();new Thread(myYield ).start();new Thread(myYield ).start();}}class MyYield implements Runnable{@Overridepublic void run(){System.out.println(Thread.currentThread().getName()+"线程开始了");Thread.yield();System.out.println(Thread.currentThread().getName()+"线程结束了");}}
线程强制执行(join)
Join合并线程,待此线程执行完成后,再执行其他线程,其他线程堵塞(可以想象为插队)
class MyJoin implements Runnable{@Overridepublic void run(){system.out.println("VIP");}}public static void main(String[] args){MyJoin myJoin = new MyJoin ();Thread thread = new Thread(myJoin);thread.start();for(int i=0;i<1000;i++){if(i==200){thread.join();}system.out.println("main"+i);}}}
线程优先级(先设置再启动):
线程优先级用数字表示,范围从1~10Thread.MIN_PRIORITY = 1Thread.MAX_PRIORITY = 10Thread.NORM_PRIORITY = 5使用以下方式改变或获取优先级:得到当前进程的优先级:getPriority() 设置进程的优先级:setPriority(int xxx)
守护线程(daemon)
1.线程分为用户线程和守护线程(一般情况不指定线程的前提下都是用户线程)
2.虚拟机必须确保用户线程执行完毕
3.虚拟机不用等待守护线程执行完毕
4.如:后台记录操作日志,监控内存,垃圾回收等
class God implements Runnable{@Overridepublic void run(){System.out.println("上帝永远守护着你");}}class You implements Runnable{@Overridepublic void run(){for(int i=0;i<36500;i++){System.out.println("今天也很快乐");}System.out.println("Goodbye this world!");}}class TestDaemon{public static void main(String[] args){God god = new God();You you = new You();//开启守护线程Thread thread = new Thread(god);god.setDaemon(true); //默认是false表示是用户线程,正常的线程都是用户线程god.start();//开启用户线程new Thread(you).start();}}
3.线程的同步(安全性) 不同线程对象卖出了重复的票甚至还出现负数票,这在日常的买票系统中是绝对不能允许的。
出现复票和负票的原因:
复值原因:当执行到输出 当前线程执行对象的时候,一个线程对象执行完输出语句后,还未执行num–语句时,第二个线程对象紧接着也执行到了该输出语句,这就导致了出现复票的原因。
负值原因:当num等于0时,一个线程对象执行完输出语句后,再执行完num-- 语句,第二个线程对象紧接着也执行到了输出语句,这时先执行的线程对象因为已经执行了num自减语句,此时num已经变为-1,紧跟第一个线程对象其后的第二个线程对象输出的是 num等于 -1,这就是出现负值的原因。
需要队列和锁
由于同一进程的锁哥线程共享同一块存储空间,在带来方便的同时也带来了访问冲突的问题,为了确保数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,则会独占资源,其他线程必须等待,使用后释放锁即可。
但会存在以下问题:
1.一个线程持有锁会导致其他所有需要此锁的线程挂起
2.在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
3.如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
synchronized锁
1.同步块synchronized(Obj){}
obj称之为同步监听器
1.obj可以是任何对象,但是推荐使用共享资源作为同步监听器
2.同步方法中无需指定同步监视器,因为同步方法的同保护监视器就是this,就是这个对象本身,或者class
2.同步方法public synchronized void method(int args){}
synchronized 方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized 都必须获得调用该方法的对象的锁才能执行,否者线程会阻塞
public class UnSafeList { public static void main(String[] args) { List
Lock锁:(加锁:lock.Lock() 解锁:lock.unlock())
ReentrantLock类实现了Lock,他拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常见的是ReentrantLock,可以显示加锁、释放锁。
import java.util.concurrent.locks.ReentrantLock;public class TestLock implements Runnable{ //定义锁 private final ReentrantLock lock = new ReentrantLock(); int ticketNums = 10; @Override public void run() { while(true){ try { lock.lock();//加锁 if (ticketNums > 0){ System.out.println(ticketNums--); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }else{ break; } }finally { lock.unlock();//解锁,解锁一般写在finally中 } } } public static void main(String[] args) { TestLock testLock = new TestLock(); new Thread(testLock).start(); new Thread(testLock).start(); new Thread(testLock).start(); }}
4.线程的通信问题synchronized与Lock的区别:
1.Lock是显示锁(手动开启和关闭锁)synchronized是隐式锁,出了作用域自动释放
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
java提供几个方法解决线程之间的通信问题
wait():表示线程一直等待,知道其他线程通知,与sleep不同,会释放锁。
wait(long timeout):指等待的毫秒数。
notify():唤醒一个处于等待状态的线程
notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度
//生产者与消费者的问题public class TestPC { public static void main(String[] args) { SynContainer container = new SynContainer(); Produect produect = new Produect(container); Consumer consumer = new Consumer(container); new Thread(produect).start(); new Thread(consumer).start(); }}//生产者class Produect implements Runnable{ SynContainer container; public Produect(SynContainer container){ this.container = container; } @Override public void run(){ for (int i = 0; i < 100; i++) { container.push(new Chicken(i)); System.out.println("生产了"+i+"只鸡"); } }}//消费者class Consumer implements Runnable{ SynContainer container; public Consumer(SynContainer container){ this.container = container; } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("消费了---》"+container.pop().id+"只鸡"); } }}class Chicken{ int id; public Chicken(int id) { this.id = id; }}//容器class SynContainer{ //需要一个容器大小 Chicken[] chickens = new Chicken[10]; int count = 0; //生产者放入产品 public synchronized void push(Chicken chicken){ if (count == chickens.length){ //生产者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没有满,我们就需要丢入产品 chickens[count] = chicken; count++; //可以通知消费者可以消费了 this.notifyAll(); } //消费者消费产品 public synchronized Chicken pop(){ //判断能否消费 if (count == 0){ //消费者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果可以消费 count--; Chicken chicken = chickens[count]; //吃完了,通知生产者生产 this.notifyAll(); return chicken; }}
5.线程池提供的线程池API:ExecutorService和Executors
ExecutorService:真正的线程池接口,常见的子类ThreadPoolExecutor
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
// 创建单一线程的线程池
public static ExecutorService newSingleThreadExecutor();
// 创建固定数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads);
// 创建带缓存的线程池
public static ExecutorService newCachedThreadPool();
// 创建定时调度的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
// 创建流式(fork-join)线程池
public static ExecutorService newWorkStealingPool();
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TestPool { public static void main(String[] args) { //1.创建服务,创建线程池 //参数为线程池的大小 ExecutorService executorService = Executors.newFixedThreadPool(5); //给线程池100个线程任务 for (int i = 0; i < 100; i++) { MyThread myThread = new MyThread(); executorService.execute(myThread); } //2.关闭连接 executorService.shutdown(); }}class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getId()); }}