线程的调度
/*
线程的优先级:
1.
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
2.如何获取和设置当前线程的优先级:
getPriority():获取线程的优先级
setPriority(int p):设置线程的优先级
*/
class myThread extends Thread{
@Override
public void run() {
for (int i=0;i<100;i++)
//获取当前线程名字
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+":"+i);
}
}
public class ThreadTest {
//遍历100以内的所有偶数
public static void main(String[] args) {
myThread h1 = new myThread();
h1.setName("线程一");
h1.start();
//给主线程设置名称
Thread.currentThread().setName("主线程");
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+":"+i);
if(i==20){
try{
h1.join();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
两个线程的优先级都是默认的5;
可以对线程的优先级进行修改
由此看出,线程优先级高不一定先执行,但是高概率先执行。
高优先级的线程要抢占低优先级线程cpu的执行权,但是只是从概率上讲,高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
例子:创建三个窗口卖票
class myThread extends Thread{
private static int tickt=100;//静态的,这样保证三个线程是同一个ticket
//而不是三个ticket
@Override
public void run() {
while (true){
if(tickt>0){
System.out.println(getName()+":卖票,票号为:"+tickt);
tickt--;
}else{
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myThread h1 = new myThread();
myThread h2 = new myThread();
myThread h3 = new myThread();
h1.setName("窗口一");
h2.setName("窗口二");
h3.setName("窗口三");
h1.start();
h2.start();
h3.start();
}
}
该程序并不完美,出现了线程安全问题,多个票数同号,这里先忽略,后面会解决。
所以要用到第二种多线程的创建方法。
多线程的创建方法二
实现Runnable接口
/*
创建多线程的方式二:实现Runnable接口
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过调用Thread类的对象调用start()
*/
class myThread implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myThread h = new myThread();
Thread t1= new Thread(h);
t1.setName("线程一");
t1.start();
Thread t2=new Thread(h);
t2.setName("线程二");
t2.start();
}
}
实现多窗口卖票
class myThread implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}else {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//创建实现接口类的对象
myThread h = new myThread();
//将对象作为参数传入Thread构造器
//创建Thread的对象
Thread t1= new Thread(h);
t1.setName("窗口一");
t1.start();
Thread t2=new Thread(h);
t2.setName("窗口二");
t2.start();
}
}
此方式仍然存在线程安全问题,这里暂时忽略。
比较创建线程的两种方式
开发中优先选择实现Runnable接口的方式
原因:
1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况。
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的内容声明在run()中
进程:运行中的代码
进程可以细化为多个线程,每个线程拥有自己独立的栈和程序计数器。多个线程共享同一个进程中的结构。
并行:多个cpu同时执行多个任务。比如多个人同时做不同的事。
并发:一个cpu(采用时间片)同时执行多个任务。比如秒杀,多个人做同一件事。
线程的分类
守护线程、用户线程
一个java程序执行时最少有两个线程,都会启动一个jvm,每个jvm实际上就是操作系统中启动一个进程,java本身具备了垃圾回收机制,所以每个java运行时至少会启动两个线程,一个main线程,另外一个是gc垃圾回收线程。
线程的生命周期
线程的最终状态一定是死亡而不是阻塞。
线程安全问题
解决线程安全问题
问题:卖票过程中出现了冲票、错票—>线程安全问题
出现问题的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
==如何解决:==当一个线程a在操作ticket的时候,其他线程不能参与进来,知道线程a操作玩ticket时,其他线程才可以操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
在java中,我们通过同步机制来解决线程安全问题。
同步的方式,解决了线程的安全问题。操作同步代码时,只能有一个线程参与,其他线程等待。
相当于是一个单线程的过程,效率更低,但没太大影响。
方式一:同步代码块
synchronized (同步监视器){
//需要被同步的代码(即操作共享数据的代码)
//共享数据:多个线程共同操作的变量。比如ticket就是共享数据
//同步监视器,俗称:锁。任何一个类的对象都可以充当锁
}
多个线程必须要公用同一把锁。
这样写出现线程安全问题的概率为0。
同步代码块解决Runnable接口的方式创建的线程安全问题
class myThread implements Runnable{
private int ticket=100;
private Object obj=new Object();//必须new在run方法外面
//不然多个线程用的不是同一把锁
@Override
public void run() {
while (true) {
synchronized (obj){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myThread h = new myThread();
Thread t1= new Thread(h);
t1.setName("线程一");
t1.start();
Thread t2=new Thread(h);
t2.setName("线程二");
t2.start();
}
}
在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器,(具体情况具体分析),不一定所有情况都可以。
这里的this代表的是myThread的对象h。
同步代码块解决继承Thread类创建的线程安全问题
class myThread extends Thread{
private static int ticket=100;//记得用private修饰
private static Object obj=new Object();//记得要用private修饰
@Override
public void run() {
while (true) {
synchronized (obj){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myThread t1 = new myThread();
t1.setName("线程一");
myThread t2 = new myThread();
t2.setName("线程二");
t1.start();
t2.start();
//new了两个myThread对象,所以ojb和ticket要用static,不然就有两个ticket和obj
}
}
这种方式慎用this代替obj,因为this在两条线程里分别代表t1,t2两个对象。但有些情况能用。
不过可以考虑用myThread.class代替obj,也就是当前类代替obj。类也是对象且这个类是唯一的。也是具体情况具体分析。
需要被同步的代码被synchronize大括号扩住,不能扩多了也不能扩少了。
方式二:同步方法
如果操作共享数据的代码完整地声明在一个方法中,我们不妨将此方法声明同步的。
/*
使用同步方法解决实现Runnable接口的线程安全问题
*/
class myThread implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
show();
if(ticket==0)
break;
}
}
//将同步代码部分挪出来声明在方法里
//该方法用synchronize修饰(上锁)
private synchronized void show() {
//同步监视器为this,就是h
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myThread h = new myThread();
Thread t1=new Thread(h);
t1.setName("线程一");
Thread t2=new Thread(h);
t2.setName("线程二");
t1.start();
t2.start();
}
}
这里的同步监视器是this。
同样的,该方法用到解决实现继承Thread创建多线程的线程安全问题中就有问题出现,因为此时两个线程的锁(同步监视器)有两个this,分别为t1,t2。不唯一。所以需要修改。
/*
使用同步方法解决实现继承Thread的线程安全问题
*/
class myThread extends Thread{
private static int ticket=100;
@Override
public void run() {
while (true){
show();
if(ticket==0)
break;
}
}
private static synchronized void show() {
//此时的同步监视器是当前类:myThread.class
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myThread t1=new myThread();
myThread t2=new myThread();
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
关于同步方法的总结
1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明(系统有默认)
2.非静态的同步方法,同步监视器是this(当前类的对象)
3.静态的同步方法,同步监视器是当前类本身。
线程安全的单例模式之懒汉式
/*
使用同步机制将单例模式中的懒汉式改写为线程安全的
*/
public class Demo1 {
}
class Bank{
private Bank(){}
private static Bank instance=null;
private static synchronized Bank getInstance(){
//同步监视器为Bank类本身(Bank.class)
if(instance==null){
instance=new Bank();
}
return instance;
}
}
也可以用同步代码解决:
public class Demo1 {
}
class Bank{
private Bank(){}
private static Bank instance=null;
private synchronized Bank getInstance(){
/*方式一:效率稍差
synchronized (Bank.class){
if(instance==null){
instance=new Bank();
}
return instance;
}*/
//方式二:效率更高
if(instance==null){
synchronized (Bank.class){
if(instance==null){
instance=new Bank();
}
}
}
return instance;
}
}