多线程
1.基本概念
并发:两个或者多个事件在同一时间段内发生
并行:两个或者多个事件在同一时刻发生
进程:一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序依此执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建运行到消亡的过程
线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,一个进程中是可以有多个线程的
2.主线程
所有的线程对象都必须是Thread类或者其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码
单线程程序:JAVA程序中只有一个线程执行从main方法开始从上到下依次执行
创建线程类
//创建一个Thread类的子类
public class MyThread extends Thread{
//在子类中重写Thread类中的run方法,设置线程任务
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println("Thread子类方法"+i);
}
}
}
public class Demo01 {
public static void main(String[] args) {
//创建Thread子类对象
MyThread mt=new MyThread();
//调用Thread类中的start()方法,开启新的线程执行run方法
mt.start();
for (int i=0;i<20;i++){
System.out.println("main方法"+i);
}
}
}
3.Thread类
构造方法:
public Thread():分配一个新的线程对象
public Thread(String name):分配一个指定名字的新的线程对象
public Thread(Runnable target):分配一个带有指定目标新的线程对象
public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字
常用方法:
public String getName():获取当前线程名称
public void start():导致此线程开始执行
public void run():此线程要执行的任务在此处定义代码
public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停
public static Thread currentThread():返回当前正在执行的线程对象的引用
//创建一个Thread类的子类
public class MyThread extends Thread{
//在子类中重写Thread类中的run方法,设置线程任务
@Override
public void run() {
//获取线程名称
String name=getName();
System.out.println(name);
}
}
//创建一个Thread类的子类
public class MyThread extends Thread{
//在子类中重写Thread类中的run方法,设置线程任务
@Override
public void run() {
//获取线程名称
Thread t=Thread.currentThread();
System.out.println(t);
}
}
public class Demo01 {
public static void main(String[] args) {
//创建Thread子类对象
MyThread mt=new MyThread();
//调用Thread类中的start()方法,开启新的线程执行run方法
mt.start();
new MyThread().start();
//Thread-0
//Thread-1
}
}
设置线程名称:
/*
设置线程名称:
1.使用Thread类中的方法setName(名字)
void setName(String name)改变线程名称使之与name相同
2.创建一个带参数的构造方法,参数传递线程的名称,把线程名称传递给父类
让父类给子线程起一个名字 Thread(String name)分配新的Thread对象
*/
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name); //把线程名称传递给父类
// 让父类给子线程起一个名字 Thread(String name)分配新的Thread对象
}
@Override
public void run() {
//获取线程名称
System.out.println(Thread.currentThread().getName());
}
}
public class Demo01 {
public static void main(String[] args) {
MyThread mt=new MyThread();
//调用Thread类中的start()方法,开启新的线程执行run方法
mt.setName("线程名称1");
mt.start();
new MyThread("线程名称2").start();
}
}
线程sleep
public class Demo01 {
public static void main(String[] args) {
for(int i=1;i<=30;i++){
System.out.println(i);
try{
Thread.sleep(1000);
}
catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
创建线程二
Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run的无参数方法
//创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{
//2.在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
public class Demo02 {
public static void main(String[] args) {
//创建一个Runnable接口的实现类对象
Runnable run=new RunnableImpl();
//创建一个Thread类对象,开启新的线程执行run方法
Thread t=new Thread(run);
//调用Thread类中的start方法,开启新线程执行run方法
t.start();
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
Thread与Runnable区别
实现Runnable接口比继承Thread所具有的优势:
1.适合多个相同的程序代码的线程去共享同一个资源
2.可以避免JAVA中的单继承的局限性
3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
4.线程池只能放入实现Runnable或者Callable类线程,不能直接放入继承Thread类
匿名内部类方式实现线程创建
简化代码,把实现接口,重写接口中的方法,创建实现类对象合成一部完成
语法:
new 父类/接口(){
重写父类/接口中的方法
}
public class Demo03 {
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()+i);
}
}
}.start();
//线程接口方式
Runnable r=new Runnable(){
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
};
new Thread(r).start();
}
}
4.线程安全与同步
完成同步操作:
1.同步代码块
synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
语法:
synchronized(同步锁){需要同步操作的代码}
同步锁:
1.锁对象可以任意类型
2.多个线程对象,需要同一把锁
public class RunnableImpl implements Runnable{
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
while(true){
synchronized (obj){
if(ticket>0){
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
public class Demo01 {
public static void main(String[] args) {
RunnableImpl run=new RunnableImpl();
Thread to=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
to.start();
t1.start();
t2.start();
}
}
2.同步方法
使用synchronized修饰的方法,叫做同步方法。保证A线程执行该方法的时候,其他线程只能在方法外等待
语法:
public synchronized void method(){可能产生线程安全问题的代码}
对于非static方法,同步锁是this
对于static方法,我们使用当前方法所在类的字节码对象
public class RunnableImpl implements Runnable{
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
while(true){
method();
}
}
public synchronized void method(){
if(ticket>0){
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
3.锁机制
public void lock():加同步锁
public void unlock():释放同步锁
public class RunnableImpl implements Runnable{
private int ticket=100;
Object obj=new Object();
Lock l=new ReentrantLock();
@Override
public void run() {
while(true){
l.lock();
if(ticket>0){
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
l.unlock();
}
}
}
5.线程通信
概念:多个线程在处理同一个资源,但是处理动作却不相同
public void wait():在其他线程调用此对象的notify()方法或者notifyAll()导致当前线程等待
public void notify():唤醒在此对象监视器上等待的单个线程,会继续执行wait方法之后代码
public class Demo02 {
public static void main(String[] args) {
//创建锁对象
Object obj=new Object();
//创建一个消费者
new Thread(){
@Override
public void run() {
synchronized (obj){
System.out.println("需求资源");
//调用wait方法,放弃cpu的执行,进入到WAITING状态
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后代码
System.out.println("消费资源");
}
}
}.start();
//创建生产者
new Thread(){
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("产生资源");
obj.notify();
}
}
}.start();
}
}
进入到TimeWaiting两种方式:
1.使用sleep(long m)方法,在毫秒值结束之后,线程进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify()唤醒,就会自动进入到Runnable/Blocked状态
唤醒方法:
void notify():唤醒在此对象监视器上等待的单个线程
void notifyAll():唤醒所有等待线程
6.线程池
可以容纳多个线程的容器,其中的线程可以反复利用,省去了频繁的创建线程对象的操作,无需反复创建线程而消耗过多的资源
当程序第一次启动的时候,创建多个线程,保存到一个集合中;当我们想要使用线程的时候,就可以从集合中取出线程使用
Thread t=list.remove():返回的是被移除的对象
Thread t=linked.removeFirst()
当使用完毕线程,需要把线程归还给线程池
list.add(t) linked.addLast(t);
线程池优点:
1.降低资源消耗。减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止消耗过多内存
使用方法
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads:创建线程池中包含的线程数量
返回值:返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收
线程池使用步骤:
1.使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
4.调用ExecutorService中的方法shutdown销毁线程池
public class RunnableImpl implements Runnable{
@Override
public void run() {
//创建一个类,实现Runnable接口,重写run方法,设置线程任务
System.out.println(Thread.currentThread().getName()+"已经创建一个新线程");
}
}
public class Demo01 {
public static void main(String[] args) {
//使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService es= Executors.newFixedThreadPool(2);
es.submit(new RunnableImpl());
es.shutdown();
}
}
7.Lambda表达式
public class Demo03 {
//使用Lambda表达式
public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"创建新线程");
}).start();
}
}
语义分析
Runnable接口只有一个run方法的定义:
public abstract void run();
即制定了一种做事情的方案:
无参数:不需要任何条件即可执行
无返回值:该方案不产生任何结果
代码块:该方案的具体执行步骤
对于Lambda表达式:
()->System.out.println()
前面一对小括号即run方法的参数,代表不需要任何条件
中间的一个箭头代表将前面的参数传递给后面的代码
后面的输出语句即业务逻辑代码
语法:
(参数列表)->{一些重写方法的代码}
无参数无返回值
public class Demo03 {
public static void main(String[] args) {
/*Function(new Method() {
@Override
public void method() {
System.out.println("Lambda表达式");
}
}); 原方法
*/
Function(()->{
System.out.println("Lambda表达式");
});
}
private static void Function(Method m){
m.method();
}
}
有参数有返回值
public class Demo04 {
public static void main(String[] args) {
Person[] array={
new Person("Kobe",24),
new Person("James",23),
new Person("Curry",30),
new Person("Kobe",8)
};
Arrays.sort(array,(Person p1,Person p2)->{
return p1.getAge()-p2.getAge();
}); //Lambda表达式
for (Person person : array) {
System.out.println(person.getName()+":"+person.getAge());
}
}
}
省略规则:
1.小括号内参数的类型可以省略
2.如果小括号内有且仅有一个参数,则小括号可以省略
3.如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号,return关键字及语句分号