1.多线程的创建
创建多线程有四种方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 使用线程池创建
1.1. 继承Thread类
实现的步骤主要是:自定义一个类继承Thread类,并重写run方法。开启线程则直接使用start方法
代码如下:
/**
* 创建线程方式1:
* 继承 Thread 类,重写 run 方法
*
*/
public class TestThread1 extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----" + i);
}
}
public static void main(String[] args) {
//创建一个线程对象,调用start方法开启线程
TestThread1 testThread1 = new TestThread1();
testThread1.start();
//main线程,主线程
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程-----" + i);
}
}
}
结果:
tips: 调用start方法说明已经进入就绪队列,不会立马执行线程,因为资源是CPU来调度的
1.2. 实现Runnable接口
实现的步骤主要是:自定义一个类实现Runnable接口,并重写run方法。开启线程要创建一个Thread类,并把自定义类的对象放入其中,然后调用start方法
代码如下:
/**
* 创建线程方式2:
* 实现Runnable接口,重写run方法
* 执行线程需要放入该实现类,并调用start方法
*/
public class TestThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码---" + i);
}
}
public static void main(String[] args) {
//创建Runnable接口的实现类
TestThread2 testThread2 = new TestThread2();
//创建线程对象,通过线程对象来开启我们的线程,代理
Thread thread = new Thread(testThread2);
thread.start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程---" + i);
}
}
}
运行结果:
1.3. 实现Callable接口
这个方式了解即可,与上述两种区别在于 call()方法可以抛异常,run()方法不行
call()方法有返回值,可以通过get()来获取结果, run()方法没有
代码如下:
/**
* 创建线程的方式3:
* 实现Callable接口,重写call方法
* 开启线程的步骤:
* 1.创建执行服务:ExecutorService
* 2.调用submit方法提交执行
* 3.使用get方法获取结果
* 4.shutdownNow方法关闭服务
*
*/
public class TestCallable implements Callable {
@Override
public String call() throws Exception {
return "TestCallable!!!!";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable();
TestCallable t2 = new TestCallable();
TestCallable t3 = new TestCallable();
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<String> r1 = ser.submit(t1);
Future<String> r2 = ser.submit(t2);
Future<String> r3 = ser.submit(t3);
//获取结果
String s1 = r1.get();
String s2 = r2.get();
String s3 = r3.get();
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);;
//关闭服务
ser.shutdownNow();
}
}
1.4. 使用线程池创建
使用线程池创建多线程如下:
创建固定数量的线程池(指定核心线程数数量)
public class ThreadPool {
public static void main(String[] args) {
ExecutorService executorService2 = Executors.newFixedThreadPool(2);
//线程执行
try{
executorService2.execute(()->{
System.out.println("使用executor方式实现多线程.....");
//业务代码
int i = 99 / 3;
System.out.println("业务代码执行结果:" + i);
});
}catch(Exception e){
e.printStackTrace();
}finally{
//线程池用完,关闭线程池
executorService2.shutdown();
}
}
}
2. 解决线程不安全问题
2.1 -线程同步
2.1.1 -线程同步方式
同步语句块:synchronized(this){方法体} (synchronized括号后的数据必须是多线程共享的数据,才能达到多线程排队)
// 以下代码的执行原理
// 1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
// 2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
// 找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
// 占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
// 3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
// 共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
// 直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
// t2占有这把锁之后,进入同步代码块执行程序。
//
// 这样就达到了线程排队执行。
// 这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队
// 执行的这些线程对象所共享的。
synchronized (this){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
普通同步方法:修饰符 synchronized 返回值类型 方法名(形参列表){方法体}
synchronized出现在实例方法上,一定锁的是this(此方法)。不能是其他的对象了。 所以这种方式不灵活。
另外还有一个缺点:synchronized出现在实例方法上, 表示整个方法体都需要同步,可能会无故扩大同步的 范围,导致程序的执行效率降低。所以这种方式不常用。
2.2 -如何解决线程安全问题
是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择
线程同步机制。