1、多线程的方式1-实现Runnable接口(推荐使用)
优点:,可以多实现不可以多继承
/*
* 使用Thread类实现多线程的程序(多开启执行路径)
*
* 实现步骤:
* 创建Thread类对象,构造方法中,传递Runnable接口的实现类对象
* Thread类调用方法start()开启线程
*/
public class Demo_01Thread {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
Demo_02Runnable runnable = new Demo_02Runnable();
//创建Thread类对象,传递接口的实现类对象
Thread thread = new Thread(runnable);
//thread类方法start()开启线程,start方法不能执行2次。
thread.start();
//这样main中和Demo_02Runnable中run方法就同时开始执行了
for(int x = 0 ; x< 100;x++){
System.out.println("main..."+x);
}
}
}
/*
* 创建的类,实现Runnable接口
* 重写接口抽象方法,全部
*
* run()方法中,就是线程要执行的目标方法
*/
public class Demo_02Runnable implements Runnable{
public void run() {
//调用Thread类静态方法currentThread()获取,正在运行run的线程对象
//Thread t = Thread.currentThread();
//调用Thread类方法getName()获取线程名字
//System.out.println(t.getName());Thread-0
for(int x = 0 ; x< 100;x++){
//调用Thread类静态方法sleep(毫秒),休眠多少毫秒再往下执行
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("run..."+x);
}
}
}
2、多线程的方式2-继承Thread
public class Demo_01Thread {
public static void main(String[] args) {
//创建Thread子类对象
Demo_02SubThread sub = new Demo_02SubThread();
//子类对象,调用父类方法start
sub.start();
}
}
public class Demo_02SubThread extends Thread{
// private int tickets = 100;
public void run(){
//Thread-0继承方式,实现线程
System.out.println(getName()+"继承方式,实现线程");
}
}
3、多线程的方式3-匿名内部类,匿名对象,创建线程程序
/*
* 匿名内部类,匿名对象,创建线程程序
* 匿名内部类: 继承或者实现接口
* new 父类或者接口(){
* 重写抽象方法(){}
* }.方法();
*/
public class Demo_03Thread {
public static void main(String[] args) {
/*Runnable r = new Runnable(){
public void run(){
System.out.println("线程实现了");
}
};
new Thread(r).start();*/
//第一种方式
new Thread(new Runnable(){
public void run(){
System.out.println("线程实现了");
}
}).start();
//第二种方式匿名对象
new Thread(){
public void run(){
System.out.println("线程实现了2222");
}
}.start();
}
}
4、Thread类方法介绍
/*
* Thread类方法:静态方法(类名直接调用)
* static Thread currentThread()获取当前正在执行的线程对象
* 在那个线程中执行,获取的就是那个线程的对象
* Thread类非静态方法 String getName()获取线程名字,main中线程名为main
* Thread类静态方法 sleep(1000) 线程暂时休眠
*/
String name = Thread.currentThread().getName();
Thread.sleep(500);
5、线程安全问题,同步代码块、Lock的使用
synchronized补充:与同步代码块相同,java还提供了同步方法来解决同步问题。
同步方法的格式:在方法的返回值前加synchronized关键字,该方法的锁默认使用this。
如果方法为静态方法,则锁默认使用该类的字节码文件对象:类名.class
/*
* 开启3个线程,每个线程都去调用方法run
* 但是,run方法数据,是共享
*/
public class Demo_01Thread {
public static void main(String[] args) {
Demo_02Ticket ticket = new Demo_02Ticket();
Thread t0 = new Thread(ticket);
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
t0.start();
t1.start();
t2.start();
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 模拟售票的案例
* 解决安全问题: 保证一个线程没有执行完毕 ,其他的线程不运行执行
* 同步技术: 关键字
* synchronized
* 同步代码块格式
* synchronized(任意对象){
* 线程操作的所有共享数据
* }
*
* 作用: 保证没有锁的线程,永久都会被阻挡在同步之外
*
* JDK5后出现的新特性 Lock接口,实现类ReentrantLock
* Lock lock = new ReentrantLock();
*/
public class Demo_02Ticket implements Runnable {
// 成员位置,定义100张票
private int tickets = 100;
Lock lock = new ReentrantLock();
private Object obj = new Object();
@Override
public void run() {
while (true) {
//这里用lock锁也是可以的更灵活一点
//lock.lock();
// 同步代码块
synchronized (obj) {
// 进行售票操作,减法
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 出售第 " + tickets--);
}
}
//lock.unlock();
}
}
}
6、死锁的原理和实践
原理:如图所示,2个人分别拥有A\B锁,当2个人分别拿着A\B锁进入第一个同步程序后,由于2人都没有释放锁,所以都进入不了第二个同步程序,就一直等着造成死锁。
代码实现
1、创建2个锁
public class LockA {
private LockA(){}
public static final LockA locka = new LockA();
}
public class LockB {
private LockB(){}
public static final LockB lockb = new LockB();
}
2、实现Runnable接口,编写run方法,同步代码块套同步代码块
public class Demo_02DeadRunnable implements Runnable {
private boolean flag ;
public Demo_02DeadRunnable(boolean flag){
this.flag = flag;
}
public void run(){
while(true){
//对成员变量flag判断,值是true,线程先进入A同步,进去B同步
if(flag){
//A同步代码块
synchronized(LockA.locka){
System.out.println("if...locka");
//进去B同步代码块
synchronized(LockB.lockb){
System.out.println("if...lockb");
}
}
}else{
//进去B同步代码块
synchronized (LockB.lockb) {
System.out.println("else...lockb");
//进去A同步代码块
synchronized (LockA.locka) {
System.out.println("else...locka");
}
}
}
}
}
}
3、测试类,进行测试
可以多执行几次,每次都不同,因为线程是随机分配cup执行权的,可以看到2个线程进入if else第一个同步代码块就造成了死锁,一直等待
public class Demo_01DeadThread {
public static void main(String[] args) {
Demo_02DeadRunnable r0 = new Demo_02DeadRunnable(true);
Demo_02DeadRunnable r1 = new Demo_02DeadRunnable(false);
Thread t0 = new Thread(r0);
Thread t1 = new Thread(r1);
t0.start();
t1.start();
}
}
7、线程池的使用
第一种: submit(Runnable r)方式
/*
* java.util.concurrent 提供实现线程池的类和接口
* Executors类,静态方法
* public static ExecutorService newFixedThreadPool(int nThreads)
* 创建一个固定个数的线程池对象
* 返回值,返回的是ExecutorService接口的实现类对象,作用,执行
*
* ExecutorService方法,submit()提交线程任务
* submit(Runnable r)
* shutdown 关闭线程池
*/
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo_01ThreadPool {
public static void main(String[] args) {
//使用类Executors类,静态方法 newFixedThreadPool创建固定个数的线程池对象
//返回ExecutorService接口的实现类对象,执行线程
ExecutorService service = Executors.newFixedThreadPool(2);
//接口的引用service,方法submit提交线程任务
service.submit(new Demo_02Runnable());
service.submit(new Demo_02Runnable());
service.submit(new Demo_02Runnable());
service.shutdown();
}
}
public class Demo_02Runnable implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+" 线程池");
}
}
第二种:submit(Callable task) 方式
好处: 线程执行后,可以具有返回值,可以抛出异常
弊端:Callable接口适用于线程池
/*
* 实现线程程序的第三种方式,JDK5后,出现的新的特性
* java.util.concurrent.Callable接口
*
* 好处: 线程执行后,可以具有返回值,可以跑出异常
* 弊端:Callable接口适用于线程池
* ExecutorService接口方法
* <T> Future<T> submit(Callable<T> task)
* submit提交线程任务,Callable接口的实现类的对象
* 获取线程运行后的返回值
*
* Future 返回Future接口的实现类对象
* 接口中的方法 get
*/
public class Demo_03Thread {
public static void main(String[] args)throws Exception {
ExecutorService service = Executors.newFixedThreadPool(2);
//提交线程,实现Callable接口实现类对象, submit返回 Future接口的实现类对象
Future<String> f1 = service.submit(new Demo_04ThreadCallable());
//调用Future接口的实现类对象方法 get
String s1 = f1.get();
System.out.println(s1);
//submit可以提交Runnable接口的实现类,获取返回值,但是, run方法,void
Future f2 = service.submit(new Runnable(){
public void run(){}
});
System.out.println(f2.get());
service.shutdown();
}
}
import java.util.concurrent.Callable;
/*
* 实现java.util.concurrent.Callable接口的类,可以被线程执行,并且还可以返回值
* 返回String
*/
public class Demo_04ThreadCallable implements Callable<String>{
public String call(){
System.out.println(Thread.currentThread().getName()+"实现Callable接口的线程实现");
return "abc";
}
}
多线程 的生命周期
补充:-------------------------------------------------------------------------------------------------------
如何解决多线程数据安全:
1、明确哪些代码是多线程运行代码。
2、明确共享数据。
3、明确多线程运行代码中哪些语句是操作共享数据的。------------------------------------重要---------------------------------------------
---------------------------等待唤醒机制的使用---------------------------------
线程间通信安全示例:
保证input和output中同步代码块用的是同一个锁,input和output交替执行,要保证input完了开始等待,此时唤醒output,相反output完了开始等待,此时唤醒input执行。
1、创建一个类
定义了同步方法,并利用等待、唤醒机制给成员变量赋值和输出值。
input部分代码:由于类中定义的是同步方法,所以这里不用同步处理,直接赋值。
output代码:由于类中定义的是同步方法,所以这里不用同步处理,直接out方法
main代码
wait()、notify()方法介绍:
------------------------------同步方法的使用------------------------------
默认锁使用的是this当前对象,如果是静态方法那么就是当前类.class