单线程 运行途中有Expection就不会运行,而且效率较低
多线程:java的多线程是抢占性调度,优先级高就先调用,优先级相同就随机调用
创建多线程的方法
方法一
1.创建类继承Thread类作为Thread的子类
2.子类重写run方法(创建线程任务)也就是线程需要做什么
3.在main()方法中创建子类对象
4.子类对象调用start()方法开启新线程
注意:
多次启动一个线程是违法的,特别是一个线程结束后重启这个线程,是不允许的
方法二
1.创建Runnable的实现类
2.重写run()方法
3.在main()方法中创建实现类的对象
/*假设Runnable的实现类为RunnableImp*/
RunnableImp ri=new Runnable();
4.在main方法中以Runnable的实现类对象为参数构造Thread对象
/*例:*/Thread t=new Thread(ri);
5.用Thread对象调用start()开启新线程
方法二优于方法一
原因:1.继承只能继承一个而实现可以实现多个接口,若继承了Thread则不能继承其他父类
2.增强了程序的拓展性
将设置任务和开启多线程分离(解耦)
用一个thread的start方法可以开启多个不同的run(构造时传递不同的实现类对象)
/*
例:加入RunnableImp1和RunnableImp2都为Runnable的实现类
但是两个实现类的run方法内容不同
*/
public static void main(Stirng args[]){
RunnableImp1 ip1=new RunnableImp1();
Thread T=new Thread(ip1);
/*
若想开启一个RunnableImp2的线程则可以
Thread T=new Thread(new RunnableImp2 );
T.strat();//开启一个新线程
}
匿名内部类
作用:
简化代码
方法:
把子类继承父类,创建子类对象,重写父类方法一步完成
把实现类实现接口,创建实现类,重写接口方法一步完成
没有子类/接口的名字
格式:
new 父类/接口(){重写方法}
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"ycj");
}
}
}.start();//开启多线程
Runnable r=new Runnable(){//定义接口的实现类对象 多态
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"严昌敬");
}
}
};
new Thread(r).start();
//Runnable的简单写法,不用创建Runnable对象接收
new Thread(new Runnable(){//定义接口的实现类对象 多态
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"三爷");
}
}
}).start();
方法
获取线程名称:
public class Getname extends Thread {
public Getname() {
}
@Override
public void run() {
//获取线程的名称
/*
方法一:直接获取线程名称
String name = getName();
System.out.println(name);
*/
/*
方法二 先获取当前线程,在通过线程获取线程名称
Thread T=Thread.currentThread();//获取当前线程,静态方法
System.out.println(T);
String name= T.getName();
System.out.println(name);
*/
//方法二写成一句话
System.out.println(Thread.currentThread().getName());
}
}
让线程休眠
public static void main(String[] args) {
for (int i = 0; i < 60; i++) {
try{
Thread.sleep(1000);}//使进程休眠1000毫秒,需要抛出异常
catch (Exception e) {
}
System.out.println(i);
}
}
线程安全问题
若多个线程用一个共享数据,则在争夺cpu时可能产生重复或者不存在的情况
解决线程安全问题的方法
方法1.在继承的子类或者是实现类里
格式
sychronized(锁对象){
可能出现线程安全问题的代码
}
注意:
1.锁对象可以是任意的对象
2.锁对象不能重复
3.锁对象的作用
把同步代码块锁住,只让一个线程在同步代码块中执行
private int tickets=100;
//锁对象定义在外面,防止调用run方法时产生多个重复的锁对象
//任意对象
Object o=new Object();
@Override
public void run() {
while (true) {
synchronized (o) {
if (tickets > 0) {
try {
Thread.sleep(10);
}
catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "张票");
tickets--;
}
}
}
方法2
创建一个方法用sychronized修饰,方法体是可能出现多线程安全问题的代码(从开始调用共享数据开始)
public class RunnableImp2 implements Runnable{
private int tickets=100;
@Override
public void run() {
while(true)
payticket();
}
public synchronized void payticket(){
if (tickets>0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "张票");
tickets--;
}
}
}
方法三
用
java.util.concurrent.Locks.Lock接口
用lock锁处理线程安全问题的方法
lock实现提供了比sychronized方法更广泛的操作
1.在成员位置创建一个ReentrantLock()
1.在可能出现线程安全问题的代码前用调用.lock()方法
2.在可能出现线程安全问题的代码结束后用.unlock()解锁
例:
public class Demo01Lock extends Thread {
private int tickets=100;
Lock l=new ReentrantLock();//1.创建ReentrantLock对象 (多态)
@Override
public void run() {
/*
更好的方法
*/
while(true) {
l.lock();//2.在可能出现线程安全问题的代码前获取锁
if (tickets > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "张票");
tickets--;
} catch (Exception e) {
e.printStackTrace();
}
finally {
//这样可以保证无论是否有异常的出现都会释放锁,增加效率
l.unlock();//3.释放锁
}
}
if(tickets==0)
break;
}
}
用lambda和匿名内部类重写父类/接口方法
格式:new 父类名/接口名(){
重写方法
}
用lambda重写父类/接口的方法比匿名内部类更简单
格式:
new 父类/接口 对象=(父类/接口)(参数列表)->{重写的方法}
在方法中重写就可以省略构造父类/接口对象的过程
直接()->{} (在方法中用lambda重写会隐含的构造父类/接口,且自己匹配需要重写的方法)
(其实严格意义上不是构造父类和接口的对象,抽象类不能直接构造)
lambda优化
参数列表:
若参数只有一个,则参数和其类型都可以省略不写(要么一起写要么一起不写)
若有多个参数,则只能省略其参数类型
方法体(且方法体中代码只能有一句):
若有return; 则{}和return和;都可以不写(要么一起写要么一起不写)
若无return则{}和;可以不写(要么一起写要么一起不写)
注意
只有当父类/接口方法只有一个时才能用lambda重写,否则无法自动匹配