1、Thread : 线程 创建多线程的第一种方式: 继承Thread类。 创建多线程的第二种方式: 实现Runnable接口。 创建多线程的第三种方式: 实现Callable接口。 第一种方法步骤: (1)创建一个自定义类继承Thread类。 (2)这个类要重写Thread类中的run方法。 为什么是run()方法呢? 当线程启动之后,执行的代码逻辑仅是run()方法的代码逻辑。 (3)根据这个类创建线程对象。 (4)启动线程。 star()方法。
public class MyThread1 extends Thread {
@Override
public void run() {
//super.run();
//写我们要线程执行的逻辑代码。
//一般来说,被线程执行的逻辑代码都是比较耗时的,为了模拟这里的耗时,使用循环打印。
for (int i = 1; i < 200; i++) {
System.out.println(i);
}
}
}
public class MyThread1Demo {
public static void main(String[] args) {
//1、每创建一个对象,相当于创建了一个新的线程对象
// MyThread myThread = new MyThread();
//2、启动线程
// myThread.run(); //单纯的调用run()方法仅仅表示的是一个对象调用普通的方法,所以这里依旧是单线程程序
// myThread.run();
//3、要想看到多线程的效果,就必须换一种方式启动线程 : start()
// myThread.start();
//4、当一个线程对象启动多次的时候, 报错: IllegalThreadStateException 非法的线程状态异常
// myThread.start();
System.out.println("===========================================");
//正确的创建线程的方法:
//1、至少创建2个及两个以上的线程对象
MyThread1 myThread1 = new MyThread1();
MyThread1 myThread2 = new MyThread1();
MyThread1 myThread3 = new MyThread1();
//2、启动线程
myThread1.start();
myThread2.start();
myThread3.start();
System.out.println("===========================================");
//匿名内部类创建线程对象
Thread t1 = new Thread("昭阳") {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
};
t1.start();
}
}
第二种方法步骤: (1)自定义一个类实现Runnable接口。 (2)实现run()方法。 (3)创建自定义类对象。 (4)创建Thread线程对象,将自定义的对象作为参数传递到构造方法中。
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
//由于Runnable接口中没有getName()方法,所以这里无法使用获取线程对象名字。
//间接调用: 我们可以先获取当前线程的对象,然后再调用Thread类中getName()方法。
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
//1、创建自定义类对象(这里不是线程对象!!!)
MyRunnable myRunnable = new MyRunnable();
//2、创建2个线程对象
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
Thread t3 = new Thread(myRunnable,"昭阳1");
//3、给线程起名字
t1.setName("昭阳");
t2.setName("米彩");
//4、启动线程
t1.start();
t2.start();
t3.start();
System.out.println("=============================");
//匿名内部类创建线程对象
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
},"昭阳");
t4.start();
}
}
第三种方法步骤: (1)自定义类实现Callable接口。 (2)实现call方法。 (3)该线程的启动必须与线程池结合,单独无法创建线程对象启动。
public class MyCallable implements Callable {
@Override
public Object call() throws Exception { // 需要返回一个Object类型的值
Object o = new Object(); // 这里随便new一个Object的值就行了
for (int i = 1; i <= 200; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return o;
}
}
public class ThreadPoolDemo2 {
public static void main(String[] args) {
//1、创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2); // 表示线程池中最多允许2个线程在执行任务。
//2、创建自定义Callable对象
MyCallable myCallable = new MyCallable(); // 这不是一个线程对象!!!
//3、提交到线程池中执行
pool.submit(myCallable); //当myCallable作为参数传入时,被底层封装成了一个线程对象并启动执行。 结果: pool-1-thread-1:1 ... 1:200
//4、手动停止线程池
pool.shutdown();
//线程池已经被关闭了,不能再继续提交任务。若再提交,会报错。
}
}
2、线程池:
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。 而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池 创建线程池步骤: (1)创建线程池对象: Executors工厂类 下有很多获取线程池的静态方法, newFixedThreadPool是其中的一种线程池: public static ExecutorService newFixedThreadPool(int nThreads) (2)在线程池中存放线程:(可以存放哪些线程呢? Runnable、Callable) Future<?> submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。 (3)在线程池中的线程如何去运行呢? 线程池对象.submit() 提交即运行。 (4)想要结束线程的运行,怎么手动结束? void shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。 线程池关闭后就不可以再提交运行了。否则报错: RejectedExecutionException
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class ThreadPoolDemo {
public static void main(String[] args) {
//1、创建线程池对象
//public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2); // 表示线程池中最多允许2个线程在执行任务。
//2、在线程池中存放线程:
//Future<?> submit(Runnable task) 提交一个可运行的任务执行,并返回一个表示该任务的未来。
MyRunnable myRunnable = new MyRunnable(); // 这不是一个线程对象!!!
//将对象作为参数传入
pool.submit(myRunnable); //当myRunnable作为参数传入时,被底层封装成了一个线程对象并启动执行。 结果: pool-1-thread-1:1 ... 1:200
pool.submit(myRunnable); // 两个线程thread1、thread2也会抢cpu线权
//提交的线程数超过线程池的数量( > 2)也会执行,只不过是当有空闲线程位置的时候再去执行。
pool.submit(myRunnable);
//3、想结束线程的运行,可不可以手动结束呢?如果可以,怎么结束?
//void shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
pool.shutdown();
//线程池已经被关闭了,不能再继续提交任务。若再提交,会报错。
pool.submit(myRunnable); // RejectedExecutionException
}
}
3、线程安全问题原因(缺一不可): (1)是否存在多线程环境。 (2)是否存在共享数据/共享变量。 (3)是否有多条语句操作着共享数据/共享变量。 解决线程安全问题方法: 同步安全机制。 4、同步安全机制: 方案一: 同步代码块 格式: synchronized(对象){ 需要同步的代码; } 方案二: 同步方法 格式: public synchronized void xxx(){} 方案二: 加Lock锁 Lock(接口) 具体的子类:Class ReentrantLock 格式: lock() 加锁 unlock() 释放锁