多线程
进程:进入到内存的程序
线程:在进程里,开启一条到cpu的执行路径,cpu可以通过这个路径执行功能,这个路径就叫线程
单个的cpu在多个线程之间做高速切换,8核的就是可以同时执行8个线程,所以就是单线程的8倍,多线程的好处就是效率高,多个线程之间不影响
-
线程调度的方式
1.抢占式调度(优先级高的就先占领)
2.分时调度 -
主线程:执行main方法的线程
-
如何创建多线程?
1.将类声明为Thread的子类,重写run方法
子类
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run"+i);
}
}
}
执行代码
public class Lianxi {
//创建多线程
public static void main (String[] args) /*throws ParseException8*/ {
//创建Thread的子类
//重写thread的run方法.设置线程任务
//创建子类的对象
//调用thread类的start方法.来执行run方法
//start就是调用一个线程来执行run方法
//当前的线程是main线程
//run方法的线程与main线程并发
//java属于抢占式,谁的优先级高先执行谁
MyThread mt=new MyThread();
mt.start();
for (int i = 0; i <20 ; i++) {
System.out.println("main"+i);
}
}
}
我们执行以上的程序,发现run和main里面的打印是随机性执行的,这是为什么呢?
结果长这个样子
main0
run0
main1
run1
run2
main2
run3
main3
main4
new mythread会开辟一条到cpu的新路径,用来执行run方法,那么cpu就在main和run这两个路径里面选择一个线程执行,我们控制不了cpu,导致这两个线程是随机执行的,谁抢到了cpu谁执行
- 多线程的内存情况是什么样的呢?
我们还是以上面这个例子来讲,有一个main方法和一个run方法,我们压栈执行
main里面new了一个mythred,就到了堆里面去
然后调用run方法,那么就把run压栈了,这是单线程的写法(main线程)
而多线程开启的时候,调用start()方法,我们另外开启了一个新的栈空间来执行run方法
每多开一个线程,就会开一个新的栈空间来执行run
cpu就在这几个开辟的空间里面选择执行,而且因为在不同的栈空间中,所以多个线程之间不会互相影响
- 打印线程名字常见的两种方式
public class MyThread extends Thread{
@Override
public void run() {
//获取线程名称
// String name=getName();
// System.out.println(name);
//第二种获取线程名称
Thread thread=Thread.currentThread();
System.out.println(thread);
}
}
public static void main (String[] args) /*throws ParseException8*/ {
MyThread mt=new MyThread();
mt.start();
new MyThread().start();
new MyThread().start();
}
-
给线程起名字的方法
1.setName(String name)
2.直接 new Thread(String name),继承的时候需要重写构造方法 -
sleep
是当前正在执行的线程以指定的毫秒数暂停,之后线程继续执行 -
创建多线程程序的第二种方式Runnable,这个接口就一个run方法
首先实现一个接口
public class RunnableImpl implements Runnable{
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
然后把接口的实现类传进去,执行
//创建多线程
public static void main (String[] args) /*throws ParseException8*/ {
//创建一个runnable接口的实现类
//在实现类中重写runnable接口的run方法,设置线程任务
//创建一个runnable接口的实现类对象
//创建thread类对象,构造方法中传递runnable接口的实现类对象
//调用thread类的start
RunnableImpl run=new RunnableImpl();
Thread t=new Thread(run);
t.start();
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
-
实现runnable来创建多线程的好处
1.一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类,实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性,实现runnable接口的方式,把设置线程任务和开启新线程进行了分离,实现类中,重写了run方法,用来设置线程任务
所以以后尽量使用第二种方式来实现多线程 -
匿名内部类实现线程的创建(究极简化代码之术)
匿名:没有名字
内部类:写在其他类内部的类
匿名内部类作用:简化代码,把子类继承父类,重写父类的方法,创建子类对象合一步完成,把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
public class Lianxi {
//创建多线程
public static void main (String[] args) /*throws ParseException8*/ {
new Thread(){
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"Thread");
}
}
}.start();
//多态的写法?
Runnable r=new Runnable(){
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"Runnable");
}
}
};
new Thread(r).start();
//简化接口的方式,匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"简化");
}
}
}).start();
}
}
- 线程安全问题
单线程是不会出现线程安全问题的,多线程程序没有访问共享数据的时候也不会出现线程安全问题,但是多个线程使用共享数据的时候会出现安全问题
设置三个线程卖100张票
public class RunnableImpl implements Runnable{
private int ticket =100;
//卖票
@Override
public void run() {
//使用
while(true){
if(ticket>0){
//票存在,卖
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖"+ticket+"张票");
ticket--;
}
}
}
}
public class Lianxi {
//模拟卖票
public static void main (String[] args) {
RunnableImpl run=new RunnableImpl();
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
执行程序,发现出现了重复的票和不存在的票
Thread-1-->正在卖1张票
Thread-0-->正在卖1张票
Thread-2-->正在卖-1张票
为什么会出现这样的问题呢?
首先t012一起抢夺cpu的执行权,谁抢到执行谁
1.t0先抢到,进入到run方法中执行,执行到if语句它就睡眠了,因此失去了cpu的执行权
2.这个时候t2抢到了执行权,进入run,再到if,sleep,又失去执行权
3.t1同上
4.t2睡醒了,抢到了cpu的执行权,继续执行,进行卖票,输出正在卖第一张票,ticket–之后,ticket=0了,不满足>0的条件了,那么if里面的语句就不执行了
5.t1睡醒了,进行卖票,但是这个时候ticket已经变成0了,输出正在卖第0张票,ticket–到变成-1,判断一下不执行了
6.t0行了,输出正在卖第-1张票,ticket–到变成-2
- 怎么解决线程安全问题呢?
1.同步代码块,把刚才的代码都放到synchronized的大括号里面就行了
public class RunnableImpl implements Runnable{
private int ticket =100;
//创建一个锁对象
Object obj=new Object();
/**1.使用同步代码块解决
(1)通过代码中的所对象,可以使用任意的对象
(2)但是必须保证多个线程使用的锁对象是同一个
(3)把同步代码块锁住,只让线程在同步代码块中执行
synchronized (同步锁){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
*/
//卖票
@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--;
}
}
}
}
}
同步技术的原理:
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器
3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法进行卖票
t0抢到了,执行run方法,遇到synchronized代码块,这时t0会检查synchronized代码块是否有锁对象,发现有,就会获取到锁对象,进入到同步中执行
t1抢到了,执行run方法,遇到synchronized代码块,这时t0会检查synchronized代码块是否有锁对象,发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象,一直到t0执行完同步中的代码,会把锁对象归还给同步代码块t1才能获取到锁对象进入到同步中执行
也就是说,同步中的线程,没有执行完毕,不会释放锁,同步外的线程没有锁进不去同步
(感觉同步代码块就像是搞了一个带锁的房间啊,保证了一次只能只能有一个线程使用这个房间)
但是需要频繁的判断锁,归还锁,释放锁,效率比较低
2.第二种方法,使用同步方法来解决
(1)把访问了共享数据的代码抽出来,放到一个方法中
(2)给方法添加synchronized修饰符
public class RunnableImpl implements Runnable{
private int ticket =100;
//创建一个锁对象
Object obj=new Object();
//卖票
@Override
public void run() {
//使用
while(true){
payticket();
}
}
//同步方法
public synchronized void payticket(){
if(ticket>0){
//票存在,卖
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖"+ticket+"张票");
ticket--;
}
}
}
同步方法也会把方法内部的代码锁住,它的锁对象就是当前线程的实现类对象new RunnableYmpl(),也就是this
2.第二种方法的变体,静态同步方法
public class RunnableImpl implements Runnable{
private static int ticket =100;
//创建一个锁对象
Object obj=new Object();
//卖票
@Override
public void run() {
//使用
while(true){
payticket();
}
}
//同步方法
public static synchronized void payticket(){
if(ticket>0){
//票存在,卖
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖"+ticket+"张票");
ticket--;
}
}
}
就是加了一个静态的修饰符,但他的锁对象就不是this了,因为static我们知道,是属于类的而不是属于对象的,static的方法是优先于对象的,而this是创建对象之后产生的,静态方法的锁对象是本类的class属性–>class文件对象(反射),也就是Runnable.class
3.第三种方案,Lock锁
Lock接口实现了synchronized语句,它提供两个方法,一个是获取锁,一个是释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class RunnableImpl implements Runnable{
private static int ticket =100;
//Lock
Lock l=new ReentrantLock();
//卖票
@Override
public void run() {
while(true){
//在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
l.lock();
if(ticket>0){
//票存在,卖
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖"+ticket+"张票");
ticket--;
}
//在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
l.unlock();
}
}
}
就是前后加个锁再把锁卸了,还有一个更好的写法,就是吧unlock写到finally语句块里面,这样无论是否程序出现异常,都会把锁释放掉,提高效率
- 线程状态
new/runnable/blocked/time_waiting/waiting/terminated
休眠和waiting有什么区别呢,time_waiting是可以自动唤醒的,而waiting状态只有notify才能强制唤醒
time_waiting:由sleep()可以进去等待一段时间
blocked:锁阻塞状态,这个是因为同步锁在别的线程,自己没有锁对象,需要等到别人把锁释放
waiting:调用wait()就可以使线程进入等待状态,notify()可以唤醒线程,由此实现了线程之间的通信 - 等待与唤醒
下面的内容就是使用等待和唤醒实现两个线程之间的通信:
import java.util.*;
public class DemoWaitAndNotify {
//进入waiting状态
//花了5s做包子,然后调用notify唤醒顾客吃包子
//顾客和老板必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
//同步使用的锁对象必须保证唯一
//只有锁对象才能调用wait和notify
public static void main(String[] args) {
//创建锁对象
Object obj=new Object();
//创建一个顾客线程
new Thread(){
@Override
public void run() {
//保证等待和唤醒的线程只有一个执行
synchronized (obj){
System.out.println("告知老板包子数量");
try {
//调用wait方法,放弃cpu的执行,进入到waitting状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后,就会继续执行wait之后的代码
System.out.println("包子做好了开始吃");
}
}
}.start();
new Thread(){
@Override
public void run() {
//花了5s做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("告诉顾客包子可以吃了");
//实质唤醒
obj.notify();
}
}
}.start();
}
}
- 线程池
因为频繁的创建和销毁线程很麻烦,所以就创建了一个线程池的概念,使得一个线程可以复用,也就是执行完一个任务以后不被销毁
线程池可以理解为一个容器–>集合(ArrayList,HashSet,LinkedList< Thread >,HashMap)
当程序第一次启动的时候,创建多个线程,保存到一个集合中,当我们想要使用线程的时候,就可以从集合中取出来线程使用
使用的时候就linked.removeFirst()
使用完毕之后,需要把线程归还给线程池,list.add(t)
JDK1.5之后直接内置了线程池的概念,可以直接使用Executors,ExecutorService线程池接口
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DemoThread01 {
public static void main(String[] args) {
//创建一个指定线程数量的线程池
ExecutorService es= Executors.newFixedThreadPool(2);
///调用submit方法,传递线程任务的实现类,开启线程,执行run方法
es.submit(new RunnableTest());//pool-1-thread-1创建了一个新的线程
//线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
es.submit(new RunnableTest());//pool-1-thread-2创建了一个新的线程
es.submit(new RunnableTest());//pool-1-thread-1创建了一个新的线程
//shutdown销毁线程池
es.shutdown();
}
}
垃圾回收机制
NIO
数组中的最长连续子序列
import java.util.*;
public class Solution {
/**
* max increasing subsequence
* @param arr int整型一维数组 the array
* @return int整型
*/
public int MLS (int[] arr) {
// write code here
if (arr==null || arr.length==0){
return 0;
}
int res=1,sum=1;
Arrays.sort(arr);
for(int i=0;i<arr.length-1;i++){
if(arr[i+1]==arr[i]){
continue;
}else if(arr[i+1]==arr[i]+1){
sum++;
res=Math.max(res,sum);
}else{
sum=1;
}
}
return res;
}
}
二叉树的序列化和反序列化
前序遍历的序列化
与前序遍历的反序列化
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public int index=-1;
//二叉树序列化
String Serialize(TreeNode root) {
StringBuffer sb=new StringBuffer();
if(root==null){
sb.append("#,");
return sb.toString();
}
//看顺序中左右,是前序遍历
sb.append(root.val+",");
sb.append(Serialize(root.left));
sb.append(Serialize(root.right));
return sb.toString();
}
//序列反序列化
TreeNode Deserialize(String str) {
index++;
int len=str.length();
if(len<=index){
return null;
}
String[] strr=str.split(",");
TreeNode node=null;
//为什么前序遍历的反序列化是这个样子的???
if(!strr[index].equals("#")){
node=new TreeNode(Integer.valueOf(strr[index]));
node.left=Deserialize(str);//因为把index放外面了所以传原本的str就行了啦,改变的一直都是全局变量index
node.right=Deserialize(str);
}
return node;
}
}
==
6号开始要转变重点了,需要开始javaweb的项目,spring等框架的学习,争取15号之前完成一个基本的项目框架
然后改变一下刷题的方式,不要自己写了,一边看教程一边写,还是过一遍基本的想法吧