1.程序、进程、线程的理解
程序:是为完成特定任务,用某种语言编写的一组指令的集合,即一段静态的代码,静态对象(个人理解:程序是一段代码的集合,程序是“死”的,因为它并没有被加载到内存中)
进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生、存在、消亡过程-----生命周期(个人理解:被加载到内存中的程序成为了进程,即运行中的程序)
线程:进程可进一步细化为线程,是一个程序的一条内部执行路径(个人理解:一个进程中可以有一个或多个线程,其代表不同的功能)
2.并行与并发
并行:多个cpu同时执行多个任务,如不同的人做不同的事(错误理解:并行是针对单核而言(即一个cpu),他会给人一种假象--让你认为它会同时执行多个线程,实际上对于并行而言,cpu会给他们分配时间片,时间片一到就要切换到下一个线程)
并发:一个cpu(采用时间片)同时执行多个任务。如秒杀、多个人做同一件事(错误理解:并发是针对多核而言(即多个cpu),不同的cpu同时执行不同的线程即为并发)
3.创建多线程的两种方式
3.1 继承Thread类
package com.atguigu.java;
class MyThread extends Thread{
@Override
public void run() {
for(int i = 0;i < 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
MyThread myThread1 = new MyThread();
myThread1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName()+ i);
}
}
System.out.println("main线程输出");
}
}
注意:
我们启动一个线程必须调用start方法而不能用继承类的对象调用run方法
如果再启动一个线程,必须再创建该继承类的对象,然后调用start方法
详细步骤:
1.继承Thread类
2.重写run方法
3.创建该实现类的对象
4.调用start方法
start方法说明:一经调用start方法就意味着两件事--1.启动了该线程 2.调用了该线程中的run方法
3.2 实现Runnable接口
package com.atguigu.java;
// 1.采用同步方法解决线程安全的问题
class Window2 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
show();
}
}
public synchronized void show(){
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在售卖:"+ticket+"票");
ticket--;
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window2 window = new Window2();
Thread thread = new Thread(window);
Thread thread1 = new Thread(window);
Thread thread2 = new Thread(window);
thread.start();
thread1.start();
thread2.start();
}
}
详细步骤:
1.创建实现Runnable接口的实现类
2.重写Runnable中的run方法
3.创建该实现类的对象
4.创建Thread类的对象,并将实现类的对象作为参数传递到Thread类构造器中
5.调用start方法
两种实现方式的对比:优先选用实现Runnable接口的方式
1.实现的方式没有单继承的局限性
2.实现的方式更适合用来处理数据共享的问题
4.Thread类的常用方法
package com.atguigu.java;
class MyThread2 extends Thread{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0) {
// try {
// sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2();
myThread2.setName("分线程");
myThread2.setPriority(Thread.MAX_PRIORITY);
myThread2.start();
Thread.currentThread().setName("主线程");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for(int i = 0;i < 100;i++){
if(i % 2 != 0)
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
5.Thread的生命周期
6.线程的同步机制
同步代码块:
package com.atguigu.java;
// 1.使用同步代码块解决线程安全的问题
// 需要注意的问题:多个线程要共用同一把锁,任何一个类都可以用来创建一把锁
class Window implements Runnable{
private int ticket = 100;
Object object = new Object();
@Override
public void run() {
while (true){
synchronized(object){
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在售卖:"+ticket+"票");
ticket--;
}
else {
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window window = new Window();
Thread thread = new Thread(window);
Thread thread1 = new Thread(window);
Thread thread2 = new Thread(window);
thread.start();
thread1.start();
thread2.start();
}
}
注意:在使用同步代码块时需要注意同步监视器和共享变量
同步监视器:形象的来说,它就是一把锁,这把锁可以是任何一个类的对象,多个线程要共用这把锁,不能每个线程都有属于自己的一把锁,否则仍然会导致线程安全问题。
共享变量:多个线程都可以访问的变量称为共享变量
同步方法:
package com.atguigu.java;
// 1.采用同步方法解决线程安全的问题
class Window2 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
show();
}
}
public synchronized void show(){
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在售卖:"+ticket+"票");
ticket--;
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window2 window = new Window2();
Thread thread = new Thread(window);
Thread thread1 = new Thread(window);
Thread thread2 = new Thread(window);
thread.start();
thread1.start();
thread2.start();
}
}
Lock类:
package com.atguigu.java;
import java.util.concurrent.locks.ReentrantLock;
// 1.使用同步代码块解决线程安全的问题
// 需要注意的问题:多个线程要共用同一把锁,任何一个类都可以用来创建一把锁
class Window4 implements Runnable{
private int ticket = 100;
ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true){
reentrantLock.lock();
try {
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在售卖:"+ticket+"票");
ticket--;
}
else {
break;
}
} finally {
reentrantLock.unlock();
}
}
}
}
public class LockTest1 {
public static void main(String[] args) {
Window4 window = new Window4();
Thread thread = new Thread(window);
Thread thread1 = new Thread(window);
Thread thread2 = new Thread(window);
thread.start();
thread1.start();
thread2.start();
}
}
同步机制的优点:可以解决线程不安全的问题
缺点:每次执行就相当于单线程执行的过程,效率低下
7.线程通信
需要用到的三个方法:wait()、notify()、notifyAll()
package com.atguigu.java;
class Communication implements Runnable{
private int number = 1;
@Override
public void run() {
while (true){
synchronized (this){
notify();
if(number <= 100){
System.out.println(Thread.currentThread().getName()+":输出的数字为:"+number);
number++;
}else {
break;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class CommunicationTest{
public static void main(String[] args) {
Communication communication = new Communication();
Thread thread = new Thread(communication);
Thread thread1 = new Thread(communication);
thread.start();
thread1.start();
}
}
需要注意的是三个方法de调用者要是同一个同步监视器(即同一把锁)否则编译不通过
8.JDK5.0新增线程的创建方式
实现Callable接口
package com.atguigu.java;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 0;i < 100 ;i++){
if(i % 2 == 0){
System.out.println(i);
sum+=i;
}
}
return sum;
}
}
public class CallableTest {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
new Thread(futureTask).start();
try {
Integer integer = futureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程池
package com.atguigu.java;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
class MyPool implements Runnable{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
System.out.println(executorService.getClass());
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor)executorService;
threadPoolExecutor.setCorePoolSize(16);
MyPool myPool = new MyPool();
executorService.execute(myPool);
executorService.shutdown();
}
}
继承Thread类,实现Runnable接口,实现Callable接口,线程池这四种方法在开发中一般选用线程池的方法。
线程池的优点:
1.提高反应时间
2. 降低资源消耗
9.关于多线程的几个高频面试题
sleep和wait方法的异同:
异: 1.所处的类不同,sleep在Thread类中,而wait在Object中
2.使用的范围不同,sleep可以在任何地方使用,而wait只能在同步代码块或同步方法中使用
3.sleep方法使用后,不会释放同步监视器,而wait会释放同步监视器
同: 都能使当前线程阻塞
synchronized和lock的异同:
异: synchronized在执行完相应的同步代码块以后,会自动释放同步监视器,而lock需要手动开启同步(lock),结束同步时也需要手动结束(unlock)。
同: 都能解决线程安全问题